"plugin:eslint-plugin/recommended"
],
parserOptions: {
- ecmaVersion: 2020
+ ecmaVersion: 2021
},
/*
}
},
rules: {
- "eslint-plugin/consistent-output": "error",
- "eslint-plugin/no-deprecated-context-methods": "error",
+ "eslint-plugin/prefer-message-ids": "error",
"eslint-plugin/prefer-output-null": "error",
"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",
- "eslint-plugin/require-meta-type": "error",
"eslint-plugin/test-case-property-ordering": "error",
"eslint-plugin/test-case-shorthand-strings": "error",
"internal-rules/multiline-comment-style": "error"
files: ["lib/rules/*", "tools/internal-rules/*"],
excludedFiles: ["index.js"],
rules: {
- "internal-rules/no-invalid-meta": "error",
- "internal-rules/consistent-meta-messages": "error"
+ "internal-rules/no-invalid-meta": "error"
}
},
{
files: ["lib/rules/*"],
excludedFiles: ["index.js"],
rules: {
- "internal-rules/consistent-docs-url": "error"
+ "eslint-plugin/require-meta-docs-url": ["error", { pattern: "https://eslint.org/docs/rules/{{name}}" }]
}
},
{
// Restrict relative path imports
{
files: ["lib/*"],
+ excludedFiles: ["lib/unsupported-api.js"],
rules: {
"node/no-restricted-require": ["error", [
...createInternalFilesPatterns()
+++ /dev/null
----
-name: "\U0001F41E Bug report"
-about: Report an issue with ESLint or rules bundled with ESLint
-title: ''
-labels: bug, repro:needed
-assignees: ''
-
----
-
-<!--
- ESLint adheres to the Open JS Foundation Code of Conduct:
- https://eslint.org/conduct
-
- This template is for bug reports. If you are here for another reason, please see below:
-
- 1. To propose a new rule: https://eslint.org/docs/developer-guide/contributing/new-rules
- 2. To request a rule change: https://eslint.org/docs/developer-guide/contributing/rule-changes
- 3. To request a change that is not a bug fix, rule change, or new rule: https://eslint.org/docs/developer-guide/contributing/changes
- 4. If you have any questions, please stop by our chatroom: https://eslint.org/chat/help
-
- Note that leaving sections blank will make it difficult for us to troubleshoot and we may have to close the issue.
--->
-
-
-**Tell us about your environment**
-
-<!--
- If you are using ESLint v6.5.0 or later, you can run ESLint with the `--env-info` flag and paste the output here.
--->
-
-* **ESLint Version:**
-* **Node Version:**
-* **npm Version:**
-* **Operating System:**
-
-**What parser (default, `@babel/eslint-parser`, `@typescript-eslint/parser`, etc.) are you using?**
-
-<!-- if you are using a parser other than ESLint's default, please try using the default parser to verify if the issue is parser-specific -->
-
-**Please show your full configuration:**
-
-<details>
-<summary>Configuration</summary>
-
-<!-- Paste your configuration below: -->
-```js
-
-```
-
-</details>
-
-**What did you do? Please include the actual source code causing the issue, as well as the command that you used to run ESLint.**
-
-<!-- Paste the source code below: -->
-```js
-
-```
-
-<!-- Paste the command you used to run ESLint: -->
-```bash
-
-```
-
-**What did you expect to happen?**
-
-
-**What actually happened? Please copy-paste the actual, raw output from ESLint.**
-
-
-**Steps to reproduce this issue:**
-
-<!-- Please tell us exactly how to see the issue you're describing -->
-
-1.
-1.
-1.
-
-**Are you willing to submit a pull request to fix this bug?**
+++ /dev/null
----
-name: "\U0001F4DD Non-rule change request"
-about: Request a change that is not a bug fix, rule change, or new rule
-title: ''
-labels: enhancement, triage, core
-assignees: ''
-
----
-
-<!--
- ESLint adheres to the Open JS Foundation Code of Conduct:
- https://eslint.org/conduct
-
- This template is for requesting a change that is not a bug fix, rule change, or new rule. If you are here for another reason, please see below:
-
- 1. To report a bug: https://eslint.org/docs/developer-guide/contributing/reporting-bugs
- 2. To request a rule change: https://eslint.org/docs/developer-guide/contributing/rule-changes
- 3. To propose a new rule: https://eslint.org/docs/developer-guide/contributing/new-rules
- 4. If you have any questions, please stop by our chatroom: https://eslint.org/chat
-
- Note that leaving sections blank will make it difficult for us to troubleshoot and we may have to close the issue.
--->
-
-
-
-**The version of ESLint you are using.**
-
-
-**The problem you want to solve.**
-
-
-**Your take on the correct solution to problem.**
-
-
-**Are you willing to submit a pull request to implement this change?**
+++ /dev/null
----
-name: "\U0001F680 New rule proposal"
-about: Propose a new rule to be added to ESLint
-title: ''
-labels: triage, rule, feature
-assignees: ''
-
----
-
-<!--
- ESLint adheres to the Open JS Foundation Code of Conduct:
- https://eslint.org/conduct
-
- This template is for new rule proposals. If you are proposing a new rule, please continue on. If you are here for another reason, please see below:
-
- 1. To report a bug: https://eslint.org/docs/developer-guide/contributing/reporting-bugs
- 2. To request a rule change: https://eslint.org/docs/developer-guide/contributing/rule-changes
- 3. To request a change that is not a bug fix, rule change, or new rule: https://eslint.org/docs/developer-guide/contributing/changes
- 4. If you have any questions, please stop by our chatroom: https://eslint.org/chat
-
- Note that leaving sections blank will make it difficult for us to troubleshoot and we may have to close the issue.
--->
-
-
-**Please describe what the rule should do:**
-
-
-**What new ECMAScript feature does this rule relate to?**
-
-<!-- New rules must be related to ECMAScript features added within the last 12 months -->
-
-**What category of rule is this? (place an "X" next to just one item)**
-
-[ ] Warns about a potential error (problem)
-[ ] Suggests an alternate way of doing something (suggestion)
-[ ] Other (please specify:)
-
-**Provide 2-3 code examples that this rule will warn about:**
-
-<!-- Put your code examples here -->
-```js
-
-```
-
-**Why should this rule be included in ESLint (instead of a plugin)?**
-
-
-**Are you willing to submit a pull request to implement this rule?**
--- /dev/null
+---
+name: "\U0001F4DD Request new syntax support"
+about: Request new stage 4 syntax be supported.
+title: ''
+labels:
+ - core
+ - new syntax
+assignees: ''
+
+---
+
+<!--
+ ESLint adheres to the Open JS Foundation Code of Conduct:
+ https://eslint.org/conduct
+-->
+
+**What is the name of the syntax to implement?**
+
+<!-- for example, "class fields" -->
+
+**Please provide the TC39 URL for the syntax proposal:**
+
+
+
+**Please provide some example code for the new syntax:**
+
+```js
+// example code here
+```
+
+## Implementation Checklist
+
+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.
+
+- [ ] Ecma262 update: <!-- paste PR URL for this syntax here -->
+- [ ] ESTree update: <!-- paste PR URL for this ESTree update here -->
+- [ ] Acorn update: <!-- paste PR URL for this syntax here -->
+- [ ] `eslint-visitor-keys` update: <!-- paste PR URL for this syntax here -->
+- [ ] `espree` update: <!-- paste PR URL for this syntax here -->
+- [ ] `eslint-scope` update: <!-- paste PR URL for this syntax here -->
+- [ ] `eslint` update: <!-- paste PR URL for this syntax here -->
+
+**Are you willing to submit a pull request to implement this syntax?**
+++ /dev/null
----
-name: "\U0001F4DD Rule change request"
-about: Request a change to an existing rule
-title: ''
-labels: enhancement, triage, rule
-assignees: ''
-
----
-
-<!--
- ESLint adheres to the Open JS Foundation Code of Conduct:
- https://eslint.org/conduct
-
- This template is for requesting a rule change. If you are here for another reason, please see below:
-
- 1. To report a bug: https://eslint.org/docs/developer-guide/contributing/reporting-bugs
- 2. To propose a new rule: https://eslint.org/docs/developer-guide/contributing/new-rules
- 3. To request a change that is not a bug fix, rule change, or new rule: https://eslint.org/docs/developer-guide/contributing/changes
- 4. If you have any questions, please stop by our chatroom: https://eslint.org/chat
-
- Note that leaving sections blank will make it difficult for us to troubleshoot and we may have to close the issue.
--->
-
-
-**What rule do you want to change?**
-
-**Does this change cause the rule to produce more or fewer warnings?**
-
-**How will the change be implemented? (New option, new default behavior, etc.)?**
-
-**Please provide some example code that this change will affect:**
-
-<!-- Put your code examples here -->
-```js
-
-```
-
-**What does the rule currently do for this code?**
-
-**What will the rule do after it's changed?**
-
-**Are you willing to submit a pull request to implement this change?**
--- /dev/null
+name: "\U0001F41E Report a problem"
+description: "Report an issue with ESLint or rules bundled with ESLint"
+title: "Bug: (fill in)"
+labels:
+ - bug
+ - "repro:needed"
+body:
+- type: markdown
+ attributes:
+ value: By opening an issue, you agree to abide by the [Open JS Foundation Code of Conduct](https://eslint.org/conduct).
+- type: textarea
+ attributes:
+ label: Environment
+ description: |
+ Please tell us about how you're running ESLint (Run `npx eslint --env-info`.)
+ value: |
+ Node version:
+ npm version:
+ Local ESLint version:
+ Global ESLint version:
+ Operating System:
+ validations:
+ required: true
+- type: dropdown
+ attributes:
+ label: What parser are you using?
+ description: |
+ Please keep in mind that some problems are parser-specific.
+ options:
+ - "Default (Espree)"
+ - "@typescript-eslint/parser"
+ - "@babel/eslint-parser"
+ - "vue-eslint-parser"
+ - "@angular-eslint/template-parser"
+ - Other
+ validations:
+ required: true
+- type: textarea
+ attributes:
+ label: What did you do?
+ description: |
+ Please include a *minimal* reproduction case with source code, configuration file, any other information about how you're using ESLint. You can use Markdown in this field.
+ value: |
+ <details>
+ <summary>Configuration</summary>
+
+ ```
+ <!-- Paste your configuration here -->
+ ```
+ </details>
+
+ ```js
+ <!-- Paste your code here -->
+ ```
+ validations:
+ required: true
+- type: textarea
+ attributes:
+ label: What did you expect to happen?
+ description: |
+ You can use Markdown in this field.
+ validations:
+ required: true
+- type: textarea
+ attributes:
+ label: What actually happened?
+ description: |
+ Please copy-paste the actual ESLint output. You can use Markdown in this field.
+ validations:
+ required: true
+- type: checkboxes
+ attributes:
+ label: Participation
+ options:
+ - label: I am willing to submit a pull request for this issue.
+ required: false
+- type: textarea
+ attributes:
+ label: Additional comments
+ description: Is there anything else that's important for the team to know?
--- /dev/null
+name: "\U0001F4DD 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
+ - triage
+ - core
+body:
+- type: markdown
+ attributes:
+ value: By opening an issue, you agree to abide by the [Open JS Foundation Code of Conduct](https://eslint.org/conduct).
+- type: input
+ attributes:
+ label: ESLint version
+ description: |
+ What version of ESLint are you currently using? (Run `npx eslint --version`.)
+ placeholder: |
+ e.g. v8.0.0
+ validations:
+ required: true
+- type: textarea
+ attributes:
+ label: What problem do you want to solve?
+ description: |
+ Please explain your use case in as much detail as possible.
+ placeholder: |
+ ESLint currently...
+ validations:
+ required: true
+- type: textarea
+ attributes:
+ label: What do you think is the correct solution?
+ description: |
+ Please explain how you'd like to change ESLint to address the problem.
+ placeholder: |
+ I'd like ESLint to...
+ validations:
+ required: true
+- type: checkboxes
+ attributes:
+ label: Participation
+ options:
+ - label: I am willing to submit a pull request for this change.
+ required: false
+- type: textarea
+ attributes:
+ label: Additional comments
+ description: Is there anything else that's important for the team to know?
--- /dev/null
+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
+body:
+- type: markdown
+ attributes:
+ value: By opening an issue, you agree to abide by the [Open JS Foundation Code of Conduct](https://eslint.org/conduct).
+- type: input
+ attributes:
+ label: Rule details
+ description: What should the new rule do?
+ validations:
+ required: true
+- type: input
+ attributes:
+ label: Related ECMAScript feature
+ description: What new ECMAScript feature does this rule relate to? Note that we only accept new core rules related to new ECMAScript features.
+ validations:
+ required: true
+- type: dropdown
+ attributes:
+ label: What type of rule is this?
+ options:
+ - Warns about a potential problem
+ - Suggests an alternate way of doing something
+ - Enforces a formatting/stylistic preference
+ validations:
+ required: true
+- type: textarea
+ attributes:
+ label: Example code
+ description: Please provide some example JavaScript code that this rule will warn about. This field will render as JavaScript.
+ render: js
+ validations:
+ required: true
+- type: textarea
+ attributes:
+ label: Why should this rule be in the core instead of a plugin?
+ description: In general, we prefer that rules be implemented in plugins where they can be tailored to your specific use case.
+ validations:
+ required: true
+- type: checkboxes
+ attributes:
+ label: Participation
+ options:
+ - label: I am willing to submit a pull request to implement this rule.
+ required: false
+- type: textarea
+ attributes:
+ label: Additional comments
+ description: Is there anything else that's important for the team to know?
--- /dev/null
+name: "\U0001F4DD Request a rule change"
+description: "Request a change to an existing core rule"
+title: "Rule Change: (fill in)"
+labels:
+ - enhancement
+ - rule
+body:
+- type: markdown
+ attributes:
+ value: By opening an issue, you agree to abide by the [Open JS Foundation Code of Conduct](https://eslint.org/conduct).
+- type: input
+ attributes:
+ label: What rule do you want to change?
+ validations:
+ required: true
+- type: dropdown
+ attributes:
+ label: What change to do you want to make?
+ options:
+ - Generate more warnings
+ - Generate fewer warnings
+ - Implement autofix
+ - Implement suggestions
+ validations:
+ required: true
+- type: dropdown
+ attributes:
+ label: How do you think the change should be implemented?
+ options:
+ - A new option
+ - A new default behavior
+ - Other
+ validations:
+ required: true
+- type: textarea
+ attributes:
+ label: Example code
+ description: Please provide some example code that this change will affect. This field will render as JavaScript.
+ render: js
+ validations:
+ required: true
+- type: textarea
+ attributes:
+ label: What does the rule currently do for this code?
+ validations:
+ required: true
+- type: textarea
+ attributes:
+ label: What will the rule do after it's changed?
+ validations:
+ required: true
+- type: checkboxes
+ attributes:
+ label: Participation
+ options:
+ - label: I am willing to submit a pull request to implement this change.
+ required: false
+- type: textarea
+ attributes:
+ label: Additional comments
+ description: Is there anything else that's important for the team to know?
#### Prerequisites checklist
-- [ ] I have read the [contributing guidelines](https://github.com/eslint/eslint/blob/master/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)
-->
[ ] Documentation update
-[ ] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/bug-report.md))
-[ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-proposal.md))
-[ ] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-change-proposal.md))
+[ ] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/HEAD/templates/bug-report.md))
+[ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/HEAD/templates/rule-proposal.md))
+[ ] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/HEAD/templates/rule-change-proposal.md))
[ ] Add autofixing to a rule
[ ] Add a CLI option
[ ] Add something to the core
name: CI
on:
push:
- branches: [master]
+ branches: [master, main]
pull_request:
- branches: [master]
+ branches: [master, main]
jobs:
verify_files:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- - uses: actions/setup-node@v1
+ - uses: actions/setup-node@v2
+ with:
+ node-version: '14.x'
- name: Install Packages
run: npm install
- name: Lint Files
strategy:
matrix:
os: [ubuntu-latest]
- node: [16.x, 15.x, 14.x, 13.x, 12.x, 10.x, "10.12.0"]
+ node: [17.x, 16.x, 14.x, 12.x, "12.22.0"]
include:
- os: windows-latest
node: "12.x"
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- - uses: actions/setup-node@v1
+ - uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node }}
- name: Install Packages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- - uses: actions/setup-node@v1
+ - uses: actions/setup-node@v2
with:
node-version: '12'
- name: Install Packages
--- /dev/null
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
+ push:
+ branches: [master, main]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [master, main]
+ schedule:
+ - cron: '28 17 * * 5'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'javascript' ]
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
+ # Learn more:
+ # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v1
+ 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
+
+ # 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@v1
+
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 https://git.io/JvXDl
+
+ # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
+ # and modify them (or add more) to build your code if your project
+ # uses a compiled language
+
+ #- run: |
+ # make bootstrap
+ # make release
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v1
--- /dev/null
+# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
+#
+# You can adjust the behavior by modifying this file.
+# For more information, see:
+# https://github.com/actions/stale
+name: Mark stale issues and pull requests
+
+on:
+ schedule:
+ - cron: '31 22 * * *'
+
+jobs:
+ stale:
+
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ pull-requests: write
+
+ steps:
+ - uses: actions/stale@v3
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ days-before-stale: 60
+ 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.'
+ stale-pr-message: 'Oops! It looks like we lost track of this pull request. What do we want to do here? This pull request will auto-close in 7 days without an update.'
+ exempt-all-assignees: true
+ exempt-issue-labels: accepted
+ exempt-pr-labels: accepted
--- /dev/null
+CHANGELOG.md
+v8.3.0 - November 21, 2021
+
+* [`60b0a29`](https://github.com/eslint/eslint/commit/60b0a292efd1b9cdc318b1e88a0cb7bbf14860b1) feat: add `allowProperties` option to require-atomic-updates (#15238) (Milos Djermanovic)
+* [`79278a1`](https://github.com/eslint/eslint/commit/79278a14f1c8747bff8f5cb2100d8776f9d517f2) feat: update no-use-before-define for class static blocks (#15312) (Milos Djermanovic)
+* [`8aa7645`](https://github.com/eslint/eslint/commit/8aa764524cf74f0b70d184c7957dbbb5f36a5ac7) fix: update vars-on-top for class static blocks (#15306) (Milos Djermanovic)
+* [`479a4cb`](https://github.com/eslint/eslint/commit/479a4cbc70f4032d4accd48e4471629e8635d677) fix: update semi-style for class static blocks (#15309) (Milos Djermanovic)
+* [`ddd01dc`](https://github.com/eslint/eslint/commit/ddd01dcd5f14c6ddea5decca46db2f379ec35aeb) feat: update no-redeclare for class static blocks (#15313) (Milos Djermanovic)
+* [`de69cec`](https://github.com/eslint/eslint/commit/de69cec834411aeb276a525c11dc10f628df2f51) feat: update no-inner-declarations for class static blocks (#15290) (Milos Djermanovic)
+* [`e2fe7ef`](https://github.com/eslint/eslint/commit/e2fe7ef7ea0458de56bed4e4c3d5f71aaebd3f28) feat: support for private-in syntax (fixes #14811) (#15060) (Yosuke Ota)
+* [`34bc8d7`](https://github.com/eslint/eslint/commit/34bc8d7cb42d696ec56e0a3c780aa5b042285d6b) feat: Update espree and eslint-scope (#15338) (Brandon Mills)
+* [`b171cd7`](https://github.com/eslint/eslint/commit/b171cd7ec839a0481a74a613b0d48a193f16bb6b) feat: update max-depth for class static blocks (#15316) (Milos Djermanovic)
+* [`6487df3`](https://github.com/eslint/eslint/commit/6487df371496dd15272e2097e4d2c932532c8727) feat: update padded-blocks for class static blocks (#15333) (Milos Djermanovic)
+* [`194f36d`](https://github.com/eslint/eslint/commit/194f36d9c009a72ec72fa9592ea9e31f9f168a52) feat: update the complexity rule for class static blocks (#15328) (Milos Djermanovic)
+* [`3530337`](https://github.com/eslint/eslint/commit/3530337e71327d8325d0de01e8e73952010b1a08) feat: update the indent rule for class static blocks (#15324) (Milos Djermanovic)
+* [`f03cd14`](https://github.com/eslint/eslint/commit/f03cd146a97ed312d635ac7b53ba0f8d01aa8b47) feat: update lines-around-comment for class static blocks (#15323) (Milos Djermanovic)
+* [`5c64747`](https://github.com/eslint/eslint/commit/5c64747a8d7a4f896f0cbce67c7f5e7690837a9b) feat: update brace-style for class static blocks (#15322) (Milos Djermanovic)
+* [`df2f1cc`](https://github.com/eslint/eslint/commit/df2f1cc81a559bbc9eee78a3a97315e2927af764) feat: update max-statements for class static blocks (#15315) (Milos Djermanovic)
+* [`fd5a0b8`](https://github.com/eslint/eslint/commit/fd5a0b8506e4b6acd740ab966cc2c0e4ff6a4d15) feat: update prefer-const for class static blocks (#15325) (Milos Djermanovic)
+* [`b3669fd`](https://github.com/eslint/eslint/commit/b3669fde2316f136af3a16b58b0c44e8ec196cee) feat: code path analysis for class static blocks (#15282) (Milos Djermanovic)
+* [`15c1397`](https://github.com/eslint/eslint/commit/15c1397f0063931f50f31af8d110a23c6d660000) feat: update eslint-scope for class static blocks (#15321) (Milos Djermanovic)
+* [`1a1bb4b`](https://github.com/eslint/eslint/commit/1a1bb4b1ee87c1b33f2d86ef70b3d81e83377547) feat: update one-var for class static blocks (#15317) (Milos Djermanovic)
+* [`9b666e0`](https://github.com/eslint/eslint/commit/9b666e0682bacf44d2a5afa0023874b8b131b5f5) feat: update padding-line-between-statements for class static blocks (#15318) (Milos Djermanovic)
+* [`6b85426`](https://github.com/eslint/eslint/commit/6b85426c33ba7ac0206cccef39ccc875b773aeae) docs: Expand `--debug` option description in the CLI documentation (#15308) (darkred)
+* [`3ae5258`](https://github.com/eslint/eslint/commit/3ae52584296887e5fc5b0267346294bb920a00e6) docs: the strict rule does not apply to class static blocks (#15314) (Milos Djermanovic)
+* [`6d1c666`](https://github.com/eslint/eslint/commit/6d1c666d318cc9e1860e1e2c72fbfa4bdd4a2c4b) fix: update no-invalid-this and no-eval for class static blocks (#15300) (Milos Djermanovic)
+* [`d3a267f`](https://github.com/eslint/eslint/commit/d3a267f5f39167e3ee8248ae6b9cae5034d0486f) feat: update class-methods-use-this for class static blocks (#15298) (Milos Djermanovic)
+* [`cdaa541`](https://github.com/eslint/eslint/commit/cdaa54130aca7a9c8dfd76c613d0718b048401b2) feat: update no-lone-blocks for class static blocks (#15295) (Milos Djermanovic)
+* [`8611538`](https://github.com/eslint/eslint/commit/8611538b47e325c6d6b115bf3d901a26e9ac29f8) feat: update block-spacing for class static blocks (#15297) (Milos Djermanovic)
+* [`7b56844`](https://github.com/eslint/eslint/commit/7b56844ece544e501f0173f6427038c9c5e0534f) feat: update keyword-spacing for class static blocks (#15289) (Milos Djermanovic)
+* [`ea18711`](https://github.com/eslint/eslint/commit/ea1871146402a77234393613fe56a416382c7f0f) feat: update no-extra-semi for class static blocks (#15287) (Milos Djermanovic)
+* [`0f0971f`](https://github.com/eslint/eslint/commit/0f0971ffc2ca6f4513eeffdf5cfa36826c8f4543) feat: update semi rule for class static blocks (#15286) (Milos Djermanovic)
+* [`abe740c`](https://github.com/eslint/eslint/commit/abe740ce68dcc9e5413df93b3d80a2e3260f1c18) feat: add examples for block-scoped-var with class static blocks (#15302) (Milos Djermanovic)
+* [`9309841`](https://github.com/eslint/eslint/commit/9309841a6cfa85005e0bf79e20415bb9220ba46e) docs: Remove inconsistent colon in pull request docs (#15303) (Jordan Eldredge)
+* [`da238cc`](https://github.com/eslint/eslint/commit/da238cc731a9b5ecd48280e0ea4ebd8a48ebeedc) docs: remove deprecation note from lines-around-comment (#15293) (Milos Djermanovic)
+* [`1055f16`](https://github.com/eslint/eslint/commit/1055f16fc6f78cc553f0b1462e8af44244c1f84b) docs: no-unused-expressions - class static blocks don't have directives (#15283) (Milos Djermanovic)
+* [`edd8d24`](https://github.com/eslint/eslint/commit/edd8d240db8878763dbb147fb6124412c0783a42) chore: upgrade eslint-visitor-keys for class static blocks (#15277) (Milos Djermanovic)
+* [`4c55216`](https://github.com/eslint/eslint/commit/4c55216ba958fcc8c3dd29fcaa80298216a48303) docs: Add variables option to no-use-before-define (#15276) (Mathias Rasmussen)
+* [`0338fd2`](https://github.com/eslint/eslint/commit/0338fd201614247eeb21e68a26e4b4c8a74f71b0) feat: Normalize ecmaVersion to eslint-scope when using custom parser (#15268) (Yosuke Ota)
+
+v8.2.0 - November 5, 2021
+
+* [`cf5b6be`](https://github.com/eslint/eslint/commit/cf5b6be6f8144f5932cdf062d380f7c0f51e64bd) chore: update @eslint/eslintrc to avoid different versions of `js-yaml` (#15265) (Milos Djermanovic)
+* [`c9fefd2`](https://github.com/eslint/eslint/commit/c9fefd2e40348b3e02b855597707a557dc4991d5) feat: report class evaluation TDZ errors in no-use-before-define (#15134) (Milos Djermanovic)
+* [`4fd7a6c`](https://github.com/eslint/eslint/commit/4fd7a6ca7339bcbbfa6feda266dcca96684b81c6) perf: don't prepare a fix for valid code in key-spacing (#15239) (Milos Djermanovic)
+* [`c415c04`](https://github.com/eslint/eslint/commit/c415c041912a3abbf106cc5713bdcf4ef42590ac) docs: Use string rule severity in CLI examples (#15253) (Kevin Partington)
+* [`796587a`](https://github.com/eslint/eslint/commit/796587ad950f6804d60473c2b5998ed3ec71c59e) build: upgrade eslint-release to v3.2.0 to support conventional commits (#15246) (Milos Djermanovic)
+* [`12b627d`](https://github.com/eslint/eslint/commit/12b627da401c68a5081822a49068421f1bb2465c) docs: fix typo in `working-with-rules.md` (#15233) (Nitin Kumar)
+* [`a86ffc0`](https://github.com/eslint/eslint/commit/a86ffc076014d1de7eefc7456a8ccfb3a2318155) docs: fix broken anchor in configuration files (#15223) (Pierre Berger)
+* [`fda533c`](https://github.com/eslint/eslint/commit/fda533cda4b70278acfce4e21b5b1ebe52ff7a3d) chore: update `strip-ansi` dependency (#15221) (Nitin Kumar)
+* [`ee8af5f`](https://github.com/eslint/eslint/commit/ee8af5fb864b510ba6b50dcfb706b8b28fdfb74e) docs: Link to unit tests from rule documentation (#15207) (Brandon Mills)
+* [`1c0ca3c`](https://github.com/eslint/eslint/commit/1c0ca3c744dd5761d424d19c9cdcccc569dfe34c) docs: add `ci` and `perf` tags for commit (#15215) (Nitin Kumar)
+* [`67949bd`](https://github.com/eslint/eslint/commit/67949bd9f3cbda08442d2e5946feb9a4f8b22d85) ci: Remove Node 16 CI prerelease workaround (#14935) (Brandon Mills)
+
+v8.1.0 - October 22, 2021
+
+* [`446b4b3`](https://github.com/eslint/eslint/commit/446b4b3583f90dba7e0ac347b57db013aecc101d) Docs: Update commit message format docs (#15200) (Nicholas C. Zakas)
+* [`d9d84a0`](https://github.com/eslint/eslint/commit/d9d84a060362efbaac727f18e3a790098bf0bc4b) Fix: keyword-spacing conflict with space-infix-ops on `>` (fixes #14712) (#15172) (Milos Djermanovic)
+* [`a1f7ad7`](https://github.com/eslint/eslint/commit/a1f7ad77e2da00ac7d6daade547fe6bef4ef6003) Fix: allow `baseConfig` to extend preloaded plugin config (fixes #15079) (#15187) (Milos Djermanovic)
+* [`3d370fb`](https://github.com/eslint/eslint/commit/3d370fb3596ccd3463c29f1a7a1e3f321dd8083a) New: Add no-unused-private-class-members rule (fixes #14859) (#14895) (Tim van der Lippe)
+* [`e926b17`](https://github.com/eslint/eslint/commit/e926b1735c77bf55abc1150b060a535a6c4e2778) New: Add name to RuleTester (#15179) (Gareth Jones)
+* [`90a5b6b`](https://github.com/eslint/eslint/commit/90a5b6b4aeff7343783f85418c683f2c9901ab07) Chore: improve performance of `:function` selector (#15181) (Milos Djermanovic)
+* [`31af1c8`](https://github.com/eslint/eslint/commit/31af1c8770c7dac9e9686a0549af329abe5a795b) Chore: fix counting of files in performance test (#15190) (Milos Djermanovic)
+* [`1b87fa8`](https://github.com/eslint/eslint/commit/1b87fa835892d9da3b945db763196715d8088090) Build: add node v17 (#15193) (唯然)
+* [`0fb3bb2`](https://github.com/eslint/eslint/commit/0fb3bb2af3301c92ccd46ece739644a17df89bab) Docs: remove `instanceof` from keyword-spacing docs (#15180) (Milos Djermanovic)
+* [`249a040`](https://github.com/eslint/eslint/commit/249a04070f88d2c895af3b78d60d2eff2730730e) Upgrade: `eslint-plugin-eslint-plugin` to v4 (#15169) (Bryan Mishkin)
+* [`35f3254`](https://github.com/eslint/eslint/commit/35f3254d5f8027f75a6cb35b58bea10037003be8) Docs: Describe range in rule docs (fixes #14162) (#15174) (Nicholas C. Zakas)
+* [`b5049c8`](https://github.com/eslint/eslint/commit/b5049c89a00f1a0da59ecaee74b9b024ef3c3621) Chore: Update stale bot settings (#15173) (Nicholas C. Zakas)
+* [`2b32f50`](https://github.com/eslint/eslint/commit/2b32f50460d6858367b25df20b7a717528891e0d) Docs: Fix typo in README.md (#15168) (Dmitriy Fishman)
+* [`dd58cd4`](https://github.com/eslint/eslint/commit/dd58cd4afa6ced9016c091fc99a702c97a3e44f0) Chore: migrate master to main (#15062) (Nitesh Seram)
+* [`ec0f8e0`](https://github.com/eslint/eslint/commit/ec0f8e0bb7d7ce502ca68fcd13ac323eb6307455) Chore: Add stale issue/PR checker (#15151) (Nicholas C. Zakas)
+* [`2cfbd4b`](https://github.com/eslint/eslint/commit/2cfbd4bfd90b31cd728d6595bd1e36667715c84d) Docs: Update README team and sponsors (ESLint Jenkins)
+
+v8.0.1 - October 13, 2021
+
+* [`f9217e5`](https://github.com/eslint/eslint/commit/f9217e527e1c49c6244400c4a58b6d1c14de51db) Upgrade: @eslint/eslintrc@1.0.3 for Jest workaround (#15164) (Brandon Mills)
+* [`c584a63`](https://github.com/eslint/eslint/commit/c584a63e2d6d9c0a66e5c5a5d43bc8148c054f5d) Chore: add ecmaVersion 13 to types.js (#15163) (Milos Djermanovic)
+* [`ff5fcd4`](https://github.com/eslint/eslint/commit/ff5fcd4d9bf43354a1b85d1f7ec1c4e1c0e5cbd9) Docs: add 13 as allowed ecma version (fixes #15159) (#15162) (唯然)
+
+v8.0.0 - October 9, 2021
+
+* [`7d3f7f0`](https://github.com/eslint/eslint/commit/7d3f7f01281671c4761f8da0d3ae9882a38eca8a) Upgrade: unfrozen @eslint/eslintrc (fixes #15036) (#15146) (Brandon Mills)
+* [`2174a6f`](https://github.com/eslint/eslint/commit/2174a6f0e5d18b673604d31e3ca7b790cdc9429b) Fix: require-atomic-updates property assignment message (fixes #15076) (#15109) (Milos Djermanovic)
+* [`f885fe0`](https://github.com/eslint/eslint/commit/f885fe06a0a79d91fc72a132fd31edf9ef0502cd) Docs: add note and example for extending the range of fix (refs #13706) (#13748) (Milos Djermanovic)
+* [`3da1509`](https://github.com/eslint/eslint/commit/3da1509106f508f0eb8ba48cdfc666225fda7edc) Docs: Add jsdoc `type` annotation to sample rule (#15085) (Bryan Mishkin)
+* [`68a49a9`](https://github.com/eslint/eslint/commit/68a49a9446c3286bb9ff24b90713c794b7e1f6f5) Docs: Update Rollup Integrations (#15142) (xiaohai)
+* [`d867f81`](https://github.com/eslint/eslint/commit/d867f8100737bb82742debee2b5dc853c5f07c91) Docs: Remove a dot from curly link (#15128) (Mauro Murru)
+* [`9f8b919`](https://github.com/eslint/eslint/commit/9f8b91922839b9d438df6cc1d542eea0509ef122) Sponsors: Sync README with website (ESLint Jenkins)
+* [`4b08f29`](https://github.com/eslint/eslint/commit/4b08f299a172d3eef09e97e85d19a1612e83ac45) Sponsors: Sync README with website (ESLint Jenkins)
+* [`ebc1ba1`](https://github.com/eslint/eslint/commit/ebc1ba1416834b7a52d1e16909ba05c731e97ed4) Sponsors: Sync README with website (ESLint Jenkins)
+* [`2d654f1`](https://github.com/eslint/eslint/commit/2d654f115f6e05b59c85434e75cf68204b976f22) Docs: add example .eslintrc.json (#15087) (Nicolas Mattia)
+* [`16034f0`](https://github.com/eslint/eslint/commit/16034f09ae6c7a78b8268b4c859928f18de7b9d6) Docs: fix fixable example (#15107) (QiChang Li)
+* [`07175b8`](https://github.com/eslint/eslint/commit/07175b8e9532d79e55c499aa27f79f023abda3c3) 8.0.0-rc.0 (ESLint Jenkins)
+* [`71faa38`](https://github.com/eslint/eslint/commit/71faa38adada4bd2f1ec0da7e45e6c7c84d1671d) Build: changelog update for 8.0.0-rc.0 (ESLint Jenkins)
+* [`67c0074`](https://github.com/eslint/eslint/commit/67c0074fa843fab629f464ff875007a8ee33cc7f) Update: Suggest missing rule in flat config (fixes #14027) (#15074) (Nicholas C. Zakas)
+* [`cf34e5c`](https://github.com/eslint/eslint/commit/cf34e5cf5ed5d09eb53c16cca06821c4e34b7b70) Update: space-before-blocks ignore after switch colons (fixes #15082) (#15093) (Milos Djermanovic)
+* [`c9efb5f`](https://github.com/eslint/eslint/commit/c9efb5f91937dcb6c8f3d7cb2f59940046d77901) Fix: preserve formatting when rules are removed from disable directives (#15081) (Milos Djermanovic)
+* [`14a4739`](https://github.com/eslint/eslint/commit/14a4739ab2233acef995a6dde233de05d067a0f3) Update: `no-new-func` rule catching eval case of `MemberExpression` (#14860) (Mojtaba Samimi)
+* [`7f2346b`](https://github.com/eslint/eslint/commit/7f2346b40ffd0d470092e52b995d7ab2648089db) Docs: Update release blog post template (#15094) (Nicholas C. Zakas)
+* [`fabdf8a`](https://github.com/eslint/eslint/commit/fabdf8a4e2f82b5fe2f903f015c3e60747a0b143) Chore: Remove `target.all` from `Makefile.js` (#15088) (Hirotaka Tagawa / wafuwafu13)
+* [`e3cd141`](https://github.com/eslint/eslint/commit/e3cd1414489ceda460d593ac7e7b14f8ad45d4fc) Sponsors: Sync README with website (ESLint Jenkins)
+* [`05d7140`](https://github.com/eslint/eslint/commit/05d7140d46e2b5300d4dc9a60450eed956c95420) Chore: document target global in Makefile.js (#15084) (Hirotaka Tagawa / wafuwafu13)
+* [`0a1a850`](https://github.com/eslint/eslint/commit/0a1a850575ca75db017051abe5e931f0f9c8012b) Update: include `ruleId` in error logs (fixes #15037) (#15053) (Ari Perkkiö)
+* [`47be800`](https://github.com/eslint/eslint/commit/47be8003d700bc0606495ae42610eaba94e639c5) Chore: test Property > .key with { a = 1 } pattern (fixes #14799) (#15072) (Milos Djermanovic)
+* [`a744dfa`](https://github.com/eslint/eslint/commit/a744dfa1f077afe406014f84135f8d26e9a12a94) Docs: Update CLA info (#15058) (Brian Warner)
+* [`9fb0f70`](https://github.com/eslint/eslint/commit/9fb0f7040759ea23538997648f2d2d53e7c9db8a) Chore: fix bug report template (#15061) (Milos Djermanovic)
+* [`f87e199`](https://github.com/eslint/eslint/commit/f87e199e988f42fc490890eee0642d86c48c85ff) Chore: Cleanup issue templates (#15039) (Nicholas C. Zakas)
+* [`660f075`](https://github.com/eslint/eslint/commit/660f075386d0b700faf1a1a94cde9d51899738a3) 8.0.0-beta.2 (ESLint Jenkins)
+* [`d148ffd`](https://github.com/eslint/eslint/commit/d148ffdec385e832956c748e36941e598b57b031) Build: changelog update for 8.0.0-beta.2 (ESLint Jenkins)
+* [`9e5c2e8`](https://github.com/eslint/eslint/commit/9e5c2e853ace560876c2f2119e134639be8659d0) Upgrade: @eslint/eslintrc@1.0.1 (#15047) (Milos Djermanovic)
+* [`7cf96cf`](https://github.com/eslint/eslint/commit/7cf96cf185f849d379b660072d660ec35ac5b46d) Breaking: Disallow reserved words in ES3 (fixes #15017) (#15046) (Milos Djermanovic)
+* [`88a3952`](https://github.com/eslint/eslint/commit/88a39520716bdd11f8647e47c57bd8bf91bc7148) Update: support class fields in the `complexity` rule (refs #14857) (#14957) (Milos Djermanovic)
+* [`9bd3d87`](https://github.com/eslint/eslint/commit/9bd3d87c8d7369e85f2b7d9b784fed8143191d30) Fix: semicolon-less style in lines-between-class-members (refs #14857) (#15045) (Milos Djermanovic)
+* [`6d1ccb6`](https://github.com/eslint/eslint/commit/6d1ccb676fedd1ceb4b1e44abf8133f116a5aecb) Update: enforceForClassFields in class-methods-use-this (refs #14857) (#15018) (YeonJuan)
+* [`91e82f5`](https://github.com/eslint/eslint/commit/91e82f5c4cfeab5ac6d01865ce0eb9ea0649df39) Docs: LintMessage.line and column are possibly undefined (#15032) (Brandon Mills)
+* [`921ba1e`](https://github.com/eslint/eslint/commit/921ba1ee53e5f2219f09050565b8d69fab517d72) Chore: fix failing cli test (#15041) (Milos Djermanovic)
+* [`dd56631`](https://github.com/eslint/eslint/commit/dd5663166a8235512e797522731af1e9651f9392) Docs: remove duplicate code path analysis document (#15033) (Milos Djermanovic)
+* [`143a598`](https://github.com/eslint/eslint/commit/143a5987f18f063a47a0646fa1e10e0f88602f6f) Chore: Switch issues to use forms (#15024) (Nicholas C. Zakas)
+* [`f966fe6`](https://github.com/eslint/eslint/commit/f966fe6286b6f668812f5155b79d4ee2a8b584b3) Fix: Update semi for class-fields (refs #14857) (#14945) (Nicholas C. Zakas)
+* [`8c61f5a`](https://github.com/eslint/eslint/commit/8c61f5ac67682fcfec7fc6faafcf72e4b1a339ff) Docs: add info about non-capturing groups to prefer-named-capture-group (#15009) (Andrzej Wódkiewicz)
+* [`dd10937`](https://github.com/eslint/eslint/commit/dd109379f730a988a9e6c0102bcfe443ad0b4b94) Update: added ignoreExpressions option to max-classes-per-file (#15000) (Josh Goldberg)
+* [`e9764f3`](https://github.com/eslint/eslint/commit/e9764f3e2fe3f7b6341c9a4381f0dcd23548338e) Fix: no-undef-init should not apply to class fields (refs #14857) (#14994) (Milos Djermanovic)
+* [`4338b74`](https://github.com/eslint/eslint/commit/4338b74767fa71e4e8d171f8503aa33d970e509f) Docs: add no-dupe-class-members examples with class fields (refs #14857) (#15005) (Milos Djermanovic)
+* [`b4232d4`](https://github.com/eslint/eslint/commit/b4232d47f88611c68a6c0f915b092b68845ecbaf) Chore: Add test that deprecated rules display a deprecated notice (#14989) (TagawaHirotaka)
+* [`88b4e3d`](https://github.com/eslint/eslint/commit/88b4e3d191c2577e2e1a283cc5f825feea6271cc) Docs: Make clear how rule options are overridden (fixes #14962) (#14976) (Jake Ob)
+* [`4165c7f`](https://github.com/eslint/eslint/commit/4165c7f937f5fc46d4209ae8f763238d73f37238) Docs: Clarify Linter vs ESLint in node.js api docs (fixes #14953) (#14995) (Brian Bartels)
+* [`80cfb8f`](https://github.com/eslint/eslint/commit/80cfb8f858888bddfefd7de6b4ecbf5aabe267bc) Docs: fix typo in migration guide (#14985) (Nitin Kumar)
+* [`1ddc955`](https://github.com/eslint/eslint/commit/1ddc9559dff437c605e33c156b4380246a231a6e) 8.0.0-beta.1 (ESLint Jenkins)
+* [`95cc61e`](https://github.com/eslint/eslint/commit/95cc61e40a89aa2278ae93ae2f35c38737280abb) Build: changelog update for 8.0.0-beta.1 (ESLint Jenkins)
+* [`05ca24c`](https://github.com/eslint/eslint/commit/05ca24c57f90f91421b682dca3d7a45b7957fb77) Update: Code path analysis for class fields (fixes #14343) (#14886) (Nicholas C. Zakas)
+* [`db15183`](https://github.com/eslint/eslint/commit/db1518374a5e88efedf1ed4609d879f3091af74f) Chore: Refactor comments of tests (#14956) (TagawaHirotaka)
+* [`396a0e3`](https://github.com/eslint/eslint/commit/396a0e3c7c82e5d2680d07250008094f336856db) Docs: update ScopeManager with class fields (#14974) (Milos Djermanovic)
+* [`6663e7a`](https://github.com/eslint/eslint/commit/6663e7aed498a73108b5e6371f218d9411b87796) Docs: remove `docs` script (fixes #14288) (#14971) (Nitin Kumar)
+* [`44c6fc8`](https://github.com/eslint/eslint/commit/44c6fc879de61e9513835d1d4d6ae978d9a43c51) Update: support class fields in func-name-matching (refs #14857) (#14964) (Milos Djermanovic)
+* [`44f7de5`](https://github.com/eslint/eslint/commit/44f7de5ee4d934dee540d3d55305126c670f6bfc) Docs: Update deprecated information (#14961) (TagawaHirotaka)
+* [`305e14a`](https://github.com/eslint/eslint/commit/305e14af8bd12afc01487abee5c9b0f3eaca989e) Breaking: remove meta.docs.category in core rules (fixes #13398) (#14594) (薛定谔的猫)
+* [`a79c9f3`](https://github.com/eslint/eslint/commit/a79c9f35d665c2bcc63267bdf359a8176e0a84ce) Chore: Enforce jsdoc check-line-alignment never (#14955) (Brett Zamir)
+* [`a8bcef7`](https://github.com/eslint/eslint/commit/a8bcef70a4a6b1fbb2007075bed754635f27ff01) Docs: Add 2021 and 2022 to supported ECMAScript versions (#14952) (coderaiser)
+* [`3409785`](https://github.com/eslint/eslint/commit/3409785a41a5bd2b128ed11b8baf7a59f9e412ee) Fix: camelcase ignoreGlobals shouldn't apply to undef vars (refs #14857) (#14966) (Milos Djermanovic)
+* [`b301069`](https://github.com/eslint/eslint/commit/b301069981dc1dcca51df2813dcebdca8c150502) Docs: fix 'When Not To Use' in prefer-named-capture-group (refs #14959) (#14969) (Milos Djermanovic)
+* [`2d18db6`](https://github.com/eslint/eslint/commit/2d18db6278320fb97bc8e0bff3518c790566a6a6) Chore: add test for merging `parserOptions` in Linter (#14948) (Milos Djermanovic)
+* [`3d7d5fb`](https://github.com/eslint/eslint/commit/3d7d5fb32425e8c04d3eaa0107a2ab03a2e285df) Update: reporting loc for `never` option in `eol-last` (refs #12334) (#14840) (Nitin Kumar)
+* [`f110926`](https://github.com/eslint/eslint/commit/f110926a7abcc875a86dd13116f794e4f950e2ba) Update: fix no-unused-vars false negative with comma operator (#14928) (Sachin)
+* [`e98f14d`](https://github.com/eslint/eslint/commit/e98f14d356b5ff934dd2a0a1fb226f1b15317ab3) Docs: Fix typo in no-implicit-globals.md (#14954) (jwbth)
+* [`9a4ae3b`](https://github.com/eslint/eslint/commit/9a4ae3b68a1afd9483d331997635727fb19a1a99) Chore: Apply comment require-description and check ClassDeclaration (#14949) (Brett Zamir)
+* [`8344675`](https://github.com/eslint/eslint/commit/8344675c309a359dd2af5afddba6122f5dc803d0) Chore: fix small typo (#14951) (Sosuke Suzuki)
+* [`26b0cd9`](https://github.com/eslint/eslint/commit/26b0cd924e79a0ab2374c0cd813e92055f9fff7b) Update: fix no-unreachable logic for class fields (refs #14857) (#14920) (Milos Djermanovic)
+* [`ee1b54f`](https://github.com/eslint/eslint/commit/ee1b54f31fa840e6ec72a313aa4090fdd3e985cd) Fix: keyword-spacing private name compat (refs #14857) (#14946) (Nicholas C. Zakas)
+* [`58840ac`](https://github.com/eslint/eslint/commit/58840ac844a61c72eabb603ecfb761812b82a7ed) Chore: Update jsdoc plugin and tweak rules in effect (#14814) (Brett Zamir)
+* [`81c60f4`](https://github.com/eslint/eslint/commit/81c60f4a8725738f191580646562d1dca7eee933) Docs: document ESLint api (#14934) (Sam Chen)
+* [`c74fe08`](https://github.com/eslint/eslint/commit/c74fe08642c30e1a4cd4e0866251a2d29466add8) Build: Force prerelease peer dep for Node 16 in CI (#14933) (Brandon Mills)
+* [`c9947d2`](https://github.com/eslint/eslint/commit/c9947d2a3e0250928d4d80f3b287f10e68fc8db2) 8.0.0-beta.0 (ESLint Jenkins)
+* [`027165c`](https://github.com/eslint/eslint/commit/027165cacf62ab1662f4c343ff30b235fd9d46b8) Build: changelog update for 8.0.0-beta.0 (ESLint Jenkins)
+* [`be334f9`](https://github.com/eslint/eslint/commit/be334f9d8633e9d193dcb8b36f484547e9d3ab97) Chore: Fix Makefile call to linter.getRules() (#14932) (Brandon Mills)
+* [`0c86b68`](https://github.com/eslint/eslint/commit/0c86b68a6e2435eb03b681b51b099b552b521adc) Chore: Replace old syntax for Array flat/flatMap (#14614) (Stephen Wade)
+* [`6a89f3f`](https://github.com/eslint/eslint/commit/6a89f3f7b6a3edb3465952521bdf06a220515b95) Chore: ignore `yarn-error.log` and `.pnpm-debug.log` (#14925) (Nitin Kumar)
+* [`28fe19c`](https://github.com/eslint/eslint/commit/28fe19c4a9108111932966aa7c9f361c26601d70) Docs: Add v8.0.0 migration guide (fixes #14856) (#14884) (Nicholas C. Zakas)
+* [`ec9db63`](https://github.com/eslint/eslint/commit/ec9db63e53a6605a558dcd82947d2425f89887c3) Upgrade: @eslint/eslintrc@1.0.0 (#14865) (Milos Djermanovic)
+* [`1f5d088`](https://github.com/eslint/eslint/commit/1f5d0889264c60dddb6fb07a3b1e43f840e84d57) Docs: add an example `Object.assign()` for rule no-import-assign (#14916) (薛定谔的猫)
+* [`af96584`](https://github.com/eslint/eslint/commit/af965848c010612c3e136c367cc9b9e2e822f580) Fix: handle computed class fields in operator-linebreak (refs #14857) (#14915) (Milos Djermanovic)
+* [`3b6cd89`](https://github.com/eslint/eslint/commit/3b6cd8934b3640ffb6fa49b471babf07f0ad769a) Chore: Add rel/abs path tests in `no-restricted-{imports/modules}` rules (#14910) (Bryan Mishkin)
+* [`62c6fe7`](https://github.com/eslint/eslint/commit/62c6fe7d10ff4eeebd196e143f96cfd88818393d) Upgrade: Debug 4.0.1 > 4.3.2 (#14892) (sandesh bafna)
+* [`f984515`](https://github.com/eslint/eslint/commit/f98451584a82e41f82ceacd484ea0fe90aa9ce63) Chore: add assertions on reporting location in `semi` (#14899) (Nitin Kumar)
+* [`a773b99`](https://github.com/eslint/eslint/commit/a773b99873965652a86bec489193dc42a8923f5f) Fix: no-useless-computed-key edge cases with class fields (refs #14857) (#14903) (Milos Djermanovic)
+* [`88db3f5`](https://github.com/eslint/eslint/commit/88db3f54988dddfbda35764ecf1ea16354c4213a) Upgrade: `js-yaml` to v4 (#14890) (Bryan Mishkin)
+* [`cbc43da`](https://github.com/eslint/eslint/commit/cbc43daad2ea229fb15a9198efd2bc2721dfb75f) Fix: prefer-destructuring PrivateIdentifier false positive (refs #14857) (#14897) (Milos Djermanovic)
+* [`ccb9a91`](https://github.com/eslint/eslint/commit/ccb9a9138acd63457e004630475495954c1be6f4) Fix: dot-notation false positive with private identifier (refs #14857) (#14898) (Milos Djermanovic)
+* [`8c35066`](https://github.com/eslint/eslint/commit/8c350660e61284c41a5cc1a5955c858db53c516b) Sponsors: Sync README with website (ESLint Jenkins)
+* [`a3dd825`](https://github.com/eslint/eslint/commit/a3dd8257252f392de5cf793c36ecab2acd955659) Sponsors: Sync README with website (ESLint Jenkins)
+* [`c4e5802`](https://github.com/eslint/eslint/commit/c4e58023f22381508babfc52087853b5e3965b9c) Docs: improve rule details for `no-console` (fixes #14793) (#14901) (Nitin Kumar)
+* [`9052eee`](https://github.com/eslint/eslint/commit/9052eee07a459dc059cd92f657a3ae73acc95bb5) Update: check class fields in no-extra-parens (refs #14857) (#14906) (Milos Djermanovic)
+* [`5c3a470`](https://github.com/eslint/eslint/commit/5c3a47072aeb5cfda40a1eb20b43a10c5ca7aab3) Docs: add class fields in no-multi-assign documentation (refs #14857) (#14907) (Milos Djermanovic)
+* [`d234d89`](https://github.com/eslint/eslint/commit/d234d890b383837f8e4bda0f6ce1e2a348f9835e) Docs: add class fields in func-names documentation (refs #14857) (#14908) (Milos Djermanovic)
+* [`ae6072b`](https://github.com/eslint/eslint/commit/ae6072b1de5c8b30ce6c58290852082439c40b30) Upgrade: `eslint-visitor-keys` to v3 (#14902) (Bryan Mishkin)
+* [`e53d8cf`](https://github.com/eslint/eslint/commit/e53d8cf9d73bd105cf6ba4f6b5477ccc4b980939) Upgrade: `markdownlint` dev dependencies (#14883) (Bryan Mishkin)
+* [`d66e941`](https://github.com/eslint/eslint/commit/d66e9414be60e05badb96bc3e1a55ca34636d7f8) Upgrade: @humanwhocodes/config-array to 0.6 (#14891) (Bryan Mishkin)
+* [`149230c`](https://github.com/eslint/eslint/commit/149230ce7e296c029a0b6c085216fc0360ed4c65) Chore: Specify Node 14.x for Verify Files CI job (#14896) (Milos Djermanovic)
+* [`537cf6a`](https://github.com/eslint/eslint/commit/537cf6a0e78ee9b7167e7f8c56f4053d3fb5b2d7) Chore: update `glob-parent` (fixes #14879)(#14887) (Nitin Kumar)
+* [`f7b4a3f`](https://github.com/eslint/eslint/commit/f7b4a3f6a44e167c71985d373f73eebd3a4d9556) Chore: update dev deps to latest (#14624) (薛定谔的猫)
+* [`24c9f2a`](https://github.com/eslint/eslint/commit/24c9f2ac57efcd699ca69695c82e51ce5742df7b) Breaking: Strict package exports (refs #13654) (#14706) (Nicholas C. Zakas)
+* [`86d31a4`](https://github.com/eslint/eslint/commit/86d31a4951e3a39e359e284f5fe336ac477369fe) Breaking: disallow SourceCode#getComments() in RuleTester (refs #14744) (#14769) (Milos Djermanovic)
+* [`1d2213d`](https://github.com/eslint/eslint/commit/1d2213deb69c5901c1950bbe648aa819e7e742ed) Breaking: Fixable disable directives (fixes #11815) (#14617) (Josh Goldberg)
+* [`4a7aab7`](https://github.com/eslint/eslint/commit/4a7aab7d4323ff7027eebca709d4e95a9aaa80bc) Breaking: require `meta` for fixable rules (fixes #13349) (#14634) (Milos Djermanovic)
+* [`d6a761f`](https://github.com/eslint/eslint/commit/d6a761f9b6582e9f71705161be827ca303ef183f) Breaking: Require `meta.hasSuggestions` for rules with suggestions (#14573) (Bryan Mishkin)
+* [`6bd747b`](https://github.com/eslint/eslint/commit/6bd747b5b7731195224875b952a9ea61445a9938) Breaking: support new regex d flag (fixes #14640) (#14653) (Yosuke Ota)
+* [`8b4f3ab`](https://github.com/eslint/eslint/commit/8b4f3abdb794feb3be31959bb44bfb0ef6318e8e) Breaking: fix comma-dangle schema (fixes #13739) (#14030) (Joakim Nilsson)
+* [`b953a4e`](https://github.com/eslint/eslint/commit/b953a4ee12f120658a9ec27d1f8ca88dd3dfb599) Breaking: upgrade espree and support new class features (refs #14343) (#14591) (Toru Nagashima)
+* [`8cce06c`](https://github.com/eslint/eslint/commit/8cce06cb39886902ce0d2e6882f46c3bf52fb955) Breaking: add some rules to eslint:recommended (refs #14673) (#14691) (薛定谔的猫)
+* [`86bb63b`](https://github.com/eslint/eslint/commit/86bb63b370e0ff350e988a5fa228a8234abe800c) Breaking: Drop `codeframe` and `table` formatters (#14316) (Federico Brigante)
+* [`f3cb320`](https://github.com/eslint/eslint/commit/f3cb3208c8952a6218d54658cfda85942b9fda42) Breaking: drop node v10/v13/v15 (fixes #14023) (#14592) (薛定谔的猫)
+* [`b8b2d55`](https://github.com/eslint/eslint/commit/b8b2d5553b0de23e8b72ee45949650cd5f9a10d2) Build: add codeql (#14729) (薛定谔的猫)
+* [`e037d61`](https://github.com/eslint/eslint/commit/e037d61a12ad17a36e05dcf65aa63fad303c79b9) Docs: Mention workaround for escaping the slash character in selectors (#14675) (Aria)
+* [`81f03b6`](https://github.com/eslint/eslint/commit/81f03b6ad69c7f67ad6ba72e02e73266aa8f7696) Docs: Update license copyright (#14877) (Nicholas C. Zakas)
+* [`fa1c07c`](https://github.com/eslint/eslint/commit/fa1c07c0d65ce21a30f5bb4a9f2ac511f8df6446) Sponsors: Sync README with website (ESLint Jenkins)
+* [`e31f492`](https://github.com/eslint/eslint/commit/e31f49206f94e2b3977ec37892d4b87ab1e46872) Sponsors: Sync README with website (ESLint Jenkins)
+* [`8307256`](https://github.com/eslint/eslint/commit/83072561b006a558d026c5a507f92945b821a0cd) Sponsors: Sync README with website (ESLint Jenkins)
+
+v8.0.0-rc.0 - September 24, 2021
+
+* [`67c0074`](https://github.com/eslint/eslint/commit/67c0074fa843fab629f464ff875007a8ee33cc7f) Update: Suggest missing rule in flat config (fixes #14027) (#15074) (Nicholas C. Zakas)
+* [`cf34e5c`](https://github.com/eslint/eslint/commit/cf34e5cf5ed5d09eb53c16cca06821c4e34b7b70) Update: space-before-blocks ignore after switch colons (fixes #15082) (#15093) (Milos Djermanovic)
+* [`c9efb5f`](https://github.com/eslint/eslint/commit/c9efb5f91937dcb6c8f3d7cb2f59940046d77901) Fix: preserve formatting when rules are removed from disable directives (#15081) (Milos Djermanovic)
+* [`14a4739`](https://github.com/eslint/eslint/commit/14a4739ab2233acef995a6dde233de05d067a0f3) Update: `no-new-func` rule catching eval case of `MemberExpression` (#14860) (Mojtaba Samimi)
+* [`7f2346b`](https://github.com/eslint/eslint/commit/7f2346b40ffd0d470092e52b995d7ab2648089db) Docs: Update release blog post template (#15094) (Nicholas C. Zakas)
+* [`fabdf8a`](https://github.com/eslint/eslint/commit/fabdf8a4e2f82b5fe2f903f015c3e60747a0b143) Chore: Remove `target.all` from `Makefile.js` (#15088) (Hirotaka Tagawa / wafuwafu13)
+* [`e3cd141`](https://github.com/eslint/eslint/commit/e3cd1414489ceda460d593ac7e7b14f8ad45d4fc) Sponsors: Sync README with website (ESLint Jenkins)
+* [`05d7140`](https://github.com/eslint/eslint/commit/05d7140d46e2b5300d4dc9a60450eed956c95420) Chore: document target global in Makefile.js (#15084) (Hirotaka Tagawa / wafuwafu13)
+* [`0a1a850`](https://github.com/eslint/eslint/commit/0a1a850575ca75db017051abe5e931f0f9c8012b) Update: include `ruleId` in error logs (fixes #15037) (#15053) (Ari Perkkiö)
+* [`47be800`](https://github.com/eslint/eslint/commit/47be8003d700bc0606495ae42610eaba94e639c5) Chore: test Property > .key with { a = 1 } pattern (fixes #14799) (#15072) (Milos Djermanovic)
+* [`a744dfa`](https://github.com/eslint/eslint/commit/a744dfa1f077afe406014f84135f8d26e9a12a94) Docs: Update CLA info (#15058) (Brian Warner)
+* [`9fb0f70`](https://github.com/eslint/eslint/commit/9fb0f7040759ea23538997648f2d2d53e7c9db8a) Chore: fix bug report template (#15061) (Milos Djermanovic)
+* [`f87e199`](https://github.com/eslint/eslint/commit/f87e199e988f42fc490890eee0642d86c48c85ff) Chore: Cleanup issue templates (#15039) (Nicholas C. Zakas)
+
+v8.0.0-beta.2 - September 10, 2021
+
+* [`9e5c2e8`](https://github.com/eslint/eslint/commit/9e5c2e853ace560876c2f2119e134639be8659d0) Upgrade: @eslint/eslintrc@1.0.1 (#15047) (Milos Djermanovic)
+* [`7cf96cf`](https://github.com/eslint/eslint/commit/7cf96cf185f849d379b660072d660ec35ac5b46d) Breaking: Disallow reserved words in ES3 (fixes #15017) (#15046) (Milos Djermanovic)
+* [`88a3952`](https://github.com/eslint/eslint/commit/88a39520716bdd11f8647e47c57bd8bf91bc7148) Update: support class fields in the `complexity` rule (refs #14857) (#14957) (Milos Djermanovic)
+* [`9bd3d87`](https://github.com/eslint/eslint/commit/9bd3d87c8d7369e85f2b7d9b784fed8143191d30) Fix: semicolon-less style in lines-between-class-members (refs #14857) (#15045) (Milos Djermanovic)
+* [`6d1ccb6`](https://github.com/eslint/eslint/commit/6d1ccb676fedd1ceb4b1e44abf8133f116a5aecb) Update: enforceForClassFields in class-methods-use-this (refs #14857) (#15018) (YeonJuan)
+* [`91e82f5`](https://github.com/eslint/eslint/commit/91e82f5c4cfeab5ac6d01865ce0eb9ea0649df39) Docs: LintMessage.line and column are possibly undefined (#15032) (Brandon Mills)
+* [`921ba1e`](https://github.com/eslint/eslint/commit/921ba1ee53e5f2219f09050565b8d69fab517d72) Chore: fix failing cli test (#15041) (Milos Djermanovic)
+* [`dd56631`](https://github.com/eslint/eslint/commit/dd5663166a8235512e797522731af1e9651f9392) Docs: remove duplicate code path analysis document (#15033) (Milos Djermanovic)
+* [`143a598`](https://github.com/eslint/eslint/commit/143a5987f18f063a47a0646fa1e10e0f88602f6f) Chore: Switch issues to use forms (#15024) (Nicholas C. Zakas)
+* [`f966fe6`](https://github.com/eslint/eslint/commit/f966fe6286b6f668812f5155b79d4ee2a8b584b3) Fix: Update semi for class-fields (refs #14857) (#14945) (Nicholas C. Zakas)
+* [`8c61f5a`](https://github.com/eslint/eslint/commit/8c61f5ac67682fcfec7fc6faafcf72e4b1a339ff) Docs: add info about non-capturing groups to prefer-named-capture-group (#15009) (Andrzej Wódkiewicz)
+* [`dd10937`](https://github.com/eslint/eslint/commit/dd109379f730a988a9e6c0102bcfe443ad0b4b94) Update: added ignoreExpressions option to max-classes-per-file (#15000) (Josh Goldberg)
+* [`e9764f3`](https://github.com/eslint/eslint/commit/e9764f3e2fe3f7b6341c9a4381f0dcd23548338e) Fix: no-undef-init should not apply to class fields (refs #14857) (#14994) (Milos Djermanovic)
+* [`4338b74`](https://github.com/eslint/eslint/commit/4338b74767fa71e4e8d171f8503aa33d970e509f) Docs: add no-dupe-class-members examples with class fields (refs #14857) (#15005) (Milos Djermanovic)
+* [`b4232d4`](https://github.com/eslint/eslint/commit/b4232d47f88611c68a6c0f915b092b68845ecbaf) Chore: Add test that deprecated rules display a deprecated notice (#14989) (TagawaHirotaka)
+* [`88b4e3d`](https://github.com/eslint/eslint/commit/88b4e3d191c2577e2e1a283cc5f825feea6271cc) Docs: Make clear how rule options are overridden (fixes #14962) (#14976) (Jake Ob)
+* [`4165c7f`](https://github.com/eslint/eslint/commit/4165c7f937f5fc46d4209ae8f763238d73f37238) Docs: Clarify Linter vs ESLint in node.js api docs (fixes #14953) (#14995) (Brian Bartels)
+* [`80cfb8f`](https://github.com/eslint/eslint/commit/80cfb8f858888bddfefd7de6b4ecbf5aabe267bc) Docs: fix typo in migration guide (#14985) (Nitin Kumar)
+
+v8.0.0-beta.1 - August 27, 2021
+
+* [`41617ec`](https://github.com/eslint/eslint/commit/41617ec3c4bc8bd1ba5f66521185be1566e6f5f4) Revert "allow all directives in line comments" (fixes #14960) (#14973) (薛定谔的猫)
+* [`05ca24c`](https://github.com/eslint/eslint/commit/05ca24c57f90f91421b682dca3d7a45b7957fb77) Update: Code path analysis for class fields (fixes #14343) (#14886) (Nicholas C. Zakas)
+* [`db15183`](https://github.com/eslint/eslint/commit/db1518374a5e88efedf1ed4609d879f3091af74f) Chore: Refactor comments of tests (#14956) (TagawaHirotaka)
+* [`396a0e3`](https://github.com/eslint/eslint/commit/396a0e3c7c82e5d2680d07250008094f336856db) Docs: update ScopeManager with class fields (#14974) (Milos Djermanovic)
+* [`6663e7a`](https://github.com/eslint/eslint/commit/6663e7aed498a73108b5e6371f218d9411b87796) Docs: remove `docs` script (fixes #14288) (#14971) (Nitin Kumar)
+* [`44c6fc8`](https://github.com/eslint/eslint/commit/44c6fc879de61e9513835d1d4d6ae978d9a43c51) Update: support class fields in func-name-matching (refs #14857) (#14964) (Milos Djermanovic)
+* [`44f7de5`](https://github.com/eslint/eslint/commit/44f7de5ee4d934dee540d3d55305126c670f6bfc) Docs: Update deprecated information (#14961) (TagawaHirotaka)
+* [`305e14a`](https://github.com/eslint/eslint/commit/305e14af8bd12afc01487abee5c9b0f3eaca989e) Breaking: remove meta.docs.category in core rules (fixes #13398) (#14594) (薛定谔的猫)
+* [`a79c9f3`](https://github.com/eslint/eslint/commit/a79c9f35d665c2bcc63267bdf359a8176e0a84ce) Chore: Enforce jsdoc check-line-alignment never (#14955) (Brett Zamir)
+* [`a8bcef7`](https://github.com/eslint/eslint/commit/a8bcef70a4a6b1fbb2007075bed754635f27ff01) Docs: Add 2021 and 2022 to supported ECMAScript versions (#14952) (coderaiser)
+* [`3409785`](https://github.com/eslint/eslint/commit/3409785a41a5bd2b128ed11b8baf7a59f9e412ee) Fix: camelcase ignoreGlobals shouldn't apply to undef vars (refs #14857) (#14966) (Milos Djermanovic)
+* [`b301069`](https://github.com/eslint/eslint/commit/b301069981dc1dcca51df2813dcebdca8c150502) Docs: fix 'When Not To Use' in prefer-named-capture-group (refs #14959) (#14969) (Milos Djermanovic)
+* [`2d18db6`](https://github.com/eslint/eslint/commit/2d18db6278320fb97bc8e0bff3518c790566a6a6) Chore: add test for merging `parserOptions` in Linter (#14948) (Milos Djermanovic)
+* [`3d7d5fb`](https://github.com/eslint/eslint/commit/3d7d5fb32425e8c04d3eaa0107a2ab03a2e285df) Update: reporting loc for `never` option in `eol-last` (refs #12334) (#14840) (Nitin Kumar)
+* [`f110926`](https://github.com/eslint/eslint/commit/f110926a7abcc875a86dd13116f794e4f950e2ba) Update: fix no-unused-vars false negative with comma operator (#14928) (Sachin)
+* [`e98f14d`](https://github.com/eslint/eslint/commit/e98f14d356b5ff934dd2a0a1fb226f1b15317ab3) Docs: Fix typo in no-implicit-globals.md (#14954) (jwbth)
+* [`9a4ae3b`](https://github.com/eslint/eslint/commit/9a4ae3b68a1afd9483d331997635727fb19a1a99) Chore: Apply comment require-description and check ClassDeclaration (#14949) (Brett Zamir)
+* [`8344675`](https://github.com/eslint/eslint/commit/8344675c309a359dd2af5afddba6122f5dc803d0) Chore: fix small typo (#14951) (Sosuke Suzuki)
+* [`26b0cd9`](https://github.com/eslint/eslint/commit/26b0cd924e79a0ab2374c0cd813e92055f9fff7b) Update: fix no-unreachable logic for class fields (refs #14857) (#14920) (Milos Djermanovic)
+* [`ee1b54f`](https://github.com/eslint/eslint/commit/ee1b54f31fa840e6ec72a313aa4090fdd3e985cd) Fix: keyword-spacing private name compat (refs #14857) (#14946) (Nicholas C. Zakas)
+* [`58840ac`](https://github.com/eslint/eslint/commit/58840ac844a61c72eabb603ecfb761812b82a7ed) Chore: Update jsdoc plugin and tweak rules in effect (#14814) (Brett Zamir)
+* [`81c60f4`](https://github.com/eslint/eslint/commit/81c60f4a8725738f191580646562d1dca7eee933) Docs: document ESLint api (#14934) (Sam Chen)
+* [`c74fe08`](https://github.com/eslint/eslint/commit/c74fe08642c30e1a4cd4e0866251a2d29466add8) Build: Force prerelease peer dep for Node 16 in CI (#14933) (Brandon Mills)
+
+v8.0.0-beta.0 - August 14, 2021
+
+* [`be334f9`](https://github.com/eslint/eslint/commit/be334f9d8633e9d193dcb8b36f484547e9d3ab97) Chore: Fix Makefile call to linter.getRules() (#14932) (Brandon Mills)
+* [`0c86b68`](https://github.com/eslint/eslint/commit/0c86b68a6e2435eb03b681b51b099b552b521adc) Chore: Replace old syntax for Array flat/flatMap (#14614) (Stephen Wade)
+* [`6a89f3f`](https://github.com/eslint/eslint/commit/6a89f3f7b6a3edb3465952521bdf06a220515b95) Chore: ignore `yarn-error.log` and `.pnpm-debug.log` (#14925) (Nitin Kumar)
+* [`28fe19c`](https://github.com/eslint/eslint/commit/28fe19c4a9108111932966aa7c9f361c26601d70) Docs: Add v8.0.0 migration guide (fixes #14856) (#14884) (Nicholas C. Zakas)
+* [`ec9db63`](https://github.com/eslint/eslint/commit/ec9db63e53a6605a558dcd82947d2425f89887c3) Upgrade: @eslint/eslintrc@1.0.0 (#14865) (Milos Djermanovic)
+* [`1f5d088`](https://github.com/eslint/eslint/commit/1f5d0889264c60dddb6fb07a3b1e43f840e84d57) Docs: add an example `Object.assign()` for rule no-import-assign (#14916) (薛定谔的猫)
+* [`af96584`](https://github.com/eslint/eslint/commit/af965848c010612c3e136c367cc9b9e2e822f580) Fix: handle computed class fields in operator-linebreak (refs #14857) (#14915) (Milos Djermanovic)
+* [`3b6cd89`](https://github.com/eslint/eslint/commit/3b6cd8934b3640ffb6fa49b471babf07f0ad769a) Chore: Add rel/abs path tests in `no-restricted-{imports/modules}` rules (#14910) (Bryan Mishkin)
+* [`62c6fe7`](https://github.com/eslint/eslint/commit/62c6fe7d10ff4eeebd196e143f96cfd88818393d) Upgrade: Debug 4.0.1 > 4.3.2 (#14892) (sandesh bafna)
+* [`f984515`](https://github.com/eslint/eslint/commit/f98451584a82e41f82ceacd484ea0fe90aa9ce63) Chore: add assertions on reporting location in `semi` (#14899) (Nitin Kumar)
+* [`a773b99`](https://github.com/eslint/eslint/commit/a773b99873965652a86bec489193dc42a8923f5f) Fix: no-useless-computed-key edge cases with class fields (refs #14857) (#14903) (Milos Djermanovic)
+* [`88db3f5`](https://github.com/eslint/eslint/commit/88db3f54988dddfbda35764ecf1ea16354c4213a) Upgrade: `js-yaml` to v4 (#14890) (Bryan Mishkin)
+* [`cbc43da`](https://github.com/eslint/eslint/commit/cbc43daad2ea229fb15a9198efd2bc2721dfb75f) Fix: prefer-destructuring PrivateIdentifier false positive (refs #14857) (#14897) (Milos Djermanovic)
+* [`ccb9a91`](https://github.com/eslint/eslint/commit/ccb9a9138acd63457e004630475495954c1be6f4) Fix: dot-notation false positive with private identifier (refs #14857) (#14898) (Milos Djermanovic)
+* [`8c35066`](https://github.com/eslint/eslint/commit/8c350660e61284c41a5cc1a5955c858db53c516b) Sponsors: Sync README with website (ESLint Jenkins)
+* [`a3dd825`](https://github.com/eslint/eslint/commit/a3dd8257252f392de5cf793c36ecab2acd955659) Sponsors: Sync README with website (ESLint Jenkins)
+* [`c4e5802`](https://github.com/eslint/eslint/commit/c4e58023f22381508babfc52087853b5e3965b9c) Docs: improve rule details for `no-console` (fixes #14793) (#14901) (Nitin Kumar)
+* [`9052eee`](https://github.com/eslint/eslint/commit/9052eee07a459dc059cd92f657a3ae73acc95bb5) Update: check class fields in no-extra-parens (refs #14857) (#14906) (Milos Djermanovic)
+* [`5c3a470`](https://github.com/eslint/eslint/commit/5c3a47072aeb5cfda40a1eb20b43a10c5ca7aab3) Docs: add class fields in no-multi-assign documentation (refs #14857) (#14907) (Milos Djermanovic)
+* [`d234d89`](https://github.com/eslint/eslint/commit/d234d890b383837f8e4bda0f6ce1e2a348f9835e) Docs: add class fields in func-names documentation (refs #14857) (#14908) (Milos Djermanovic)
+* [`ae6072b`](https://github.com/eslint/eslint/commit/ae6072b1de5c8b30ce6c58290852082439c40b30) Upgrade: `eslint-visitor-keys` to v3 (#14902) (Bryan Mishkin)
+* [`e53d8cf`](https://github.com/eslint/eslint/commit/e53d8cf9d73bd105cf6ba4f6b5477ccc4b980939) Upgrade: `markdownlint` dev dependencies (#14883) (Bryan Mishkin)
+* [`d66e941`](https://github.com/eslint/eslint/commit/d66e9414be60e05badb96bc3e1a55ca34636d7f8) Upgrade: @humanwhocodes/config-array to 0.6 (#14891) (Bryan Mishkin)
+* [`149230c`](https://github.com/eslint/eslint/commit/149230ce7e296c029a0b6c085216fc0360ed4c65) Chore: Specify Node 14.x for Verify Files CI job (#14896) (Milos Djermanovic)
+* [`537cf6a`](https://github.com/eslint/eslint/commit/537cf6a0e78ee9b7167e7f8c56f4053d3fb5b2d7) Chore: update `glob-parent` (fixes #14879)(#14887) (Nitin Kumar)
+* [`f7b4a3f`](https://github.com/eslint/eslint/commit/f7b4a3f6a44e167c71985d373f73eebd3a4d9556) Chore: update dev deps to latest (#14624) (薛定谔的猫)
+* [`24c9f2a`](https://github.com/eslint/eslint/commit/24c9f2ac57efcd699ca69695c82e51ce5742df7b) Breaking: Strict package exports (refs #13654) (#14706) (Nicholas C. Zakas)
+* [`86d31a4`](https://github.com/eslint/eslint/commit/86d31a4951e3a39e359e284f5fe336ac477369fe) Breaking: disallow SourceCode#getComments() in RuleTester (refs #14744) (#14769) (Milos Djermanovic)
+* [`1d2213d`](https://github.com/eslint/eslint/commit/1d2213deb69c5901c1950bbe648aa819e7e742ed) Breaking: Fixable disable directives (fixes #11815) (#14617) (Josh Goldberg)
+* [`4a7aab7`](https://github.com/eslint/eslint/commit/4a7aab7d4323ff7027eebca709d4e95a9aaa80bc) Breaking: require `meta` for fixable rules (fixes #13349) (#14634) (Milos Djermanovic)
+* [`d6a761f`](https://github.com/eslint/eslint/commit/d6a761f9b6582e9f71705161be827ca303ef183f) Breaking: Require `meta.hasSuggestions` for rules with suggestions (#14573) (Bryan Mishkin)
+* [`4c841b8`](https://github.com/eslint/eslint/commit/4c841b880b5649392a55c98ecc9af757bd213ff0) Breaking: allow all directives in line comments (fixes #14575) (#14656) (薛定谔的猫)
+* [`6bd747b`](https://github.com/eslint/eslint/commit/6bd747b5b7731195224875b952a9ea61445a9938) Breaking: support new regex d flag (fixes #14640) (#14653) (Yosuke Ota)
+* [`8b4f3ab`](https://github.com/eslint/eslint/commit/8b4f3abdb794feb3be31959bb44bfb0ef6318e8e) Breaking: fix comma-dangle schema (fixes #13739) (#14030) (Joakim Nilsson)
+* [`b953a4e`](https://github.com/eslint/eslint/commit/b953a4ee12f120658a9ec27d1f8ca88dd3dfb599) Breaking: upgrade espree and support new class features (refs #14343) (#14591) (Toru Nagashima)
+* [`8cce06c`](https://github.com/eslint/eslint/commit/8cce06cb39886902ce0d2e6882f46c3bf52fb955) Breaking: add some rules to eslint:recommended (refs #14673) (#14691) (薛定谔的猫)
+* [`86bb63b`](https://github.com/eslint/eslint/commit/86bb63b370e0ff350e988a5fa228a8234abe800c) Breaking: Drop `codeframe` and `table` formatters (#14316) (Federico Brigante)
+* [`f3cb320`](https://github.com/eslint/eslint/commit/f3cb3208c8952a6218d54658cfda85942b9fda42) Breaking: drop node v10/v13/v15 (fixes #14023) (#14592) (薛定谔的猫)
+* [`b8b2d55`](https://github.com/eslint/eslint/commit/b8b2d5553b0de23e8b72ee45949650cd5f9a10d2) Build: add codeql (#14729) (薛定谔的猫)
+* [`e037d61`](https://github.com/eslint/eslint/commit/e037d61a12ad17a36e05dcf65aa63fad303c79b9) Docs: Mention workaround for escaping the slash character in selectors (#14675) (Aria)
+* [`81f03b6`](https://github.com/eslint/eslint/commit/81f03b6ad69c7f67ad6ba72e02e73266aa8f7696) Docs: Update license copyright (#14877) (Nicholas C. Zakas)
+* [`fa1c07c`](https://github.com/eslint/eslint/commit/fa1c07c0d65ce21a30f5bb4a9f2ac511f8df6446) Sponsors: Sync README with website (ESLint Jenkins)
+* [`e31f492`](https://github.com/eslint/eslint/commit/e31f49206f94e2b3977ec37892d4b87ab1e46872) Sponsors: Sync README with website (ESLint Jenkins)
+* [`8307256`](https://github.com/eslint/eslint/commit/83072561b006a558d026c5a507f92945b821a0cd) Sponsors: Sync README with website (ESLint Jenkins)
+
+v7.32.0 - July 30, 2021
+
+* [`3c78a7b`](https://github.com/eslint/eslint/commit/3c78a7bff6044fd196ae3b737983e6744c6eb7c8) Chore: Adopt `eslint-plugin/prefer-message-ids` rule internally (#14841) (Bryan Mishkin)
+* [`faecf56`](https://github.com/eslint/eslint/commit/faecf56cdb4146b28bfa4f1980adb41b4d3614b1) Update: change reporting location for `curly` rule (refs #12334) (#14766) (Nitin Kumar)
+* [`d7dc07a`](https://github.com/eslint/eslint/commit/d7dc07a15e256cee9232183165e2f6102f2c0873) Fix: ignore lines with empty elements (fixes #12756) (#14837) (Soufiane Boutahlil)
+* [`1bfbefd`](https://github.com/eslint/eslint/commit/1bfbefdaaf19ef32df42b89a3f5d32cff1e5b831) New: Exit on fatal error (fixes #13711) (#14730) (Antonios Katopodis)
+* [`ed007c8`](https://github.com/eslint/eslint/commit/ed007c82ee9d2170c87500d98303554b5f90b915) Chore: Simplify internal `no-invalid-meta` rule (#14842) (Bryan Mishkin)
+* [`d53d906`](https://github.com/eslint/eslint/commit/d53d9064b9dd0dd6a8ea39e07b16310c8364db69) Docs: Prepare data for website to indicate rules with suggestions (#14830) (Bryan Mishkin)
+* [`d28f2ff`](https://github.com/eslint/eslint/commit/d28f2ffb986e49d6da5c1d91215580591f4cfd35) Docs: Reference eslint-config-eslint to avoid potential for staleness (#14805) (Brett Zamir)
+* [`8be8a36`](https://github.com/eslint/eslint/commit/8be8a36010145dfcd31cbdd4f781a91989e3b1bd) Chore: Adopt `eslint-plugin/require-meta-docs-url` rule internally (#14823) (Bryan Mishkin)
+* [`f9c164f`](https://github.com/eslint/eslint/commit/f9c164f7b74ca73384c8c80eed5bdbe359b44f6c) Docs: New syntax issue template (#14826) (Nicholas C. Zakas)
+* [`eba0c45`](https://github.com/eslint/eslint/commit/eba0c4595c126a91f700d5f2e8723ec3f820a830) Chore: assertions on reporting loc in `unicode-bom` (refs #12334) (#14809) (Nitin Kumar)
+* [`ed945bd`](https://github.com/eslint/eslint/commit/ed945bd662714b1917e9de71d5b322a28be9161b) Docs: fix multiple broken links (#14833) (Sam Chen)
+* [`60df44c`](https://github.com/eslint/eslint/commit/60df44c79b0f74406119c0c040a360ca84e721fc) Chore: use `actions/setup-node@v2` (#14816) (Nitin Kumar)
+* [`6641d88`](https://github.com/eslint/eslint/commit/6641d88e17d952a8e51df5e0d3882a842d4c3f35) Docs: Update README team and sponsors (ESLint Jenkins)
+
+v7.31.0 - July 17, 2021
+
+* [`efdbb12`](https://github.com/eslint/eslint/commit/efdbb1227019427ec2d968a8d6e9151dd8a77c35) Upgrade: @eslint/eslintrc to v0.4.3 (#14808) (Brandon Mills)
+* [`a96b05f`](https://github.com/eslint/eslint/commit/a96b05f6c5649cfee112d605c91d95aa191e2f78) Update: add end location to report in `consistent-return` (refs #12334) (#14798) (Nitin Kumar)
+* [`e0e8e30`](https://github.com/eslint/eslint/commit/e0e8e308929c9c66612505f2da89043f8592eea7) Docs: update BUG_REPORT template (#14787) (Nitin Kumar)
+* [`39115c8`](https://github.com/eslint/eslint/commit/39115c8b71d2629161359f6456f47fdbd552fddd) Docs: provide more context to no-eq-null (#14801) (gfyoung)
+* [`9a3c73c`](https://github.com/eslint/eslint/commit/9a3c73c130d437a65f4edba0dcb63390e68cac41) Docs: fix a broken link (#14790) (Sam Chen)
+* [`ddffa8a`](https://github.com/eslint/eslint/commit/ddffa8ad58b4b124b08061e9045fdb5370cbdbe3) Update: Indicating the operator in question (#14764) (Paul Smith)
+* [`bba714c`](https://github.com/eslint/eslint/commit/bba714c2ed813821ed288fbc07722cdde6e534fe) Update: Clarifying what changes need to be made in no-mixed-operators (#14765) (Paul Smith)
+* [`b0d22e3`](https://github.com/eslint/eslint/commit/b0d22e3eff18ea7f08189134c07cddceaec69a09) Docs: Mention benefit of providing `meta.docs.url` (#14774) (Bryan Mishkin)
+* [`000cc79`](https://github.com/eslint/eslint/commit/000cc796fd487e7b9ba8bcc5857dd691044479cc) Sponsors: Sync README with website (ESLint Jenkins)
+* [`a6a7438`](https://github.com/eslint/eslint/commit/a6a7438502abc6a1e29ec35cfbe2058ffc0803b1) Chore: pin fs-teardown@0.1.1 (#14771) (Milos Djermanovic)
+
+v7.30.0 - July 2, 2021
+
+* [`5f74642`](https://github.com/eslint/eslint/commit/5f746420700d457b92dd86659de588d272937b79) Chore: don't check Program.start in SourceCode#getComments (refs #14744) (#14748) (Milos Djermanovic)
+* [`19a871a`](https://github.com/eslint/eslint/commit/19a871a35ae9997ce352624b1081c96c54b73a9f) Docs: Suggest linting plugins for ESLint plugin developers (#14754) (Bryan Mishkin)
+* [`aa87329`](https://github.com/eslint/eslint/commit/aa87329d919f569404ca573b439934552006572f) Docs: fix broken links (#14756) (Sam Chen)
+* [`278813a`](https://github.com/eslint/eslint/commit/278813a6e759f6b5512ac64c7530c9c51732e692) Docs: fix and add more examples for new-cap rule (fixes #12874) (#14725) (Nitin Kumar)
+* [`ed1da5d`](https://github.com/eslint/eslint/commit/ed1da5d96af2587b7211854e45cf8657ef808710) Update: ecmaVersion allows "latest" (#14720) (薛定谔的猫)
+* [`104c0b5`](https://github.com/eslint/eslint/commit/104c0b592f203d315a108d311c58375357e40b24) Update: improve use-isnan rule to detect `Number.NaN` (fixes #14715) (#14718) (Nitin Kumar)
+* [`b08170b`](https://github.com/eslint/eslint/commit/b08170b92beb22db6ec612ebdfff930f9e0582ab) Update: Implement FlatConfigArray (refs #13481) (#14321) (Nicholas C. Zakas)
+* [`f113cdd`](https://github.com/eslint/eslint/commit/f113cdd872257d72bbd66d95e4eaf13623323b24) Chore: upgrade eslint-plugin-eslint-plugin (#14738) (薛定谔的猫)
+* [`1b8997a`](https://github.com/eslint/eslint/commit/1b8997ab63781f4ebf87e3269400b2ef4c7d2973) Docs: Fix getRulesMetaForResults link syntax (#14723) (Brandon Mills)
+* [`aada733`](https://github.com/eslint/eslint/commit/aada733d2aee830aa32cccb9828cd72db4ccd6bd) Docs: fix two broken links (#14726) (Sam Chen)
+* [`8972529`](https://github.com/eslint/eslint/commit/8972529f82d13bd04059ee8852b4ebb9b5350962) Docs: Update README team and sponsors (ESLint Jenkins)
+
+v7.29.0 - June 18, 2021
+
+* [`bfbfe5c`](https://github.com/eslint/eslint/commit/bfbfe5c1fd4c39a06d5e159dbe48479ca4305fc0) New: Add only to RuleTester (refs eslint/rfcs#73) (#14677) (Brandon Mills)
+* [`c2cd7b4`](https://github.com/eslint/eslint/commit/c2cd7b4a18057ca6067bdfc16de771dc5d90c0ea) New: Add ESLint#getRulesMetaForResults() (refs #13654) (#14716) (Nicholas C. Zakas)
+* [`eea7e0d`](https://github.com/eslint/eslint/commit/eea7e0d09d6ef43d6663cbe424e7974764a5f7fe) Chore: remove duplicate code (#14719) (Nitin Kumar)
+* [`6a1c7a0`](https://github.com/eslint/eslint/commit/6a1c7a0dac050ea5876972c50563a7eb867b38d3) Fix: allow fallthrough comment inside block (fixes #14701) (#14702) (Kevin Gibbons)
+* [`a47e5e3`](https://github.com/eslint/eslint/commit/a47e5e30b0da364593b6881f6826c595da8696f5) Docs: Add Mega-Linter to the list of integrations (#14707) (Nicolas Vuillamy)
+* [`353ddf9`](https://github.com/eslint/eslint/commit/353ddf965078030794419b089994373e27ffc86e) Chore: enable reportUnusedDisableDirectives in eslint-config-eslint (#14699) (薛定谔的猫)
+* [`757c495`](https://github.com/eslint/eslint/commit/757c49584a5852c468c1b4a0b74ad3aa39d954e5) Chore: add some rules to eslint-config-eslint (#14692) (薛定谔的猫)
+* [`c93a222`](https://github.com/eslint/eslint/commit/c93a222563177a9b5bc7a59aa106bc0a6d31e063) Docs: fix a broken link (#14697) (Sam Chen)
+* [`655c118`](https://github.com/eslint/eslint/commit/655c1187fc845bac61ae8d06c556f1a59ee2071b) Sponsors: Sync README with website (ESLint Jenkins)
+* [`e2bed2e`](https://github.com/eslint/eslint/commit/e2bed2ead22b575d55ccaeed94eecd3a979dd871) Sponsors: Sync README with website (ESLint Jenkins)
+* [`8490fb4`](https://github.com/eslint/eslint/commit/8490fb42e559ef0b3c34ac60be4e05e0d879a9cb) Sponsors: Sync README with website (ESLint Jenkins)
+* [`ddbe877`](https://github.com/eslint/eslint/commit/ddbe877c95224e127215d35562a175c6f2b7ba22) Sponsors: Sync README with website (ESLint Jenkins)
+
v7.28.0 - June 4, 2021
* [`1237705`](https://github.com/eslint/eslint/commit/1237705dd08c209c5e3136045ec51a4ba87a3abe) Upgrade: @eslint/eslintrc to 0.4.2 (#14672) (Milos Djermanovic)
-Copyright JS Foundation and other contributors, https://js.foundation
+Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
* @author nzakas
*/
-/* global target */
-/* eslint no-use-before-define: "off", no-console: "off" */
+/* eslint no-use-before-define: "off", no-console: "off" -- CLI */
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
-require("shelljs/make");
-
const checker = require("npm-license"),
ReleaseOps = require("eslint-release"),
dateformat = require("dateformat"),
{ CLIEngine } = require("./lib/cli-engine"),
builtinRules = require("./lib/rules/index");
+require("shelljs/make");
+/* global target -- global.target is declared in `shelljs/make.js` */
+/**
+ * global.target = {};
+ * @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, cp, echo, exec, exit, find, ls, mkdir, pwd, rm, 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
+ /MIT/u, /BSD/u, /Apache/u, /ISC/u, /WTF/u, /Public Domain/u, /LGPL/u, /Python/u
];
//------------------------------------------------------------------------------
// Files
RULE_FILES = glob.sync("lib/rules/*.js").filter(filePath => path.basename(filePath) !== "index.js"),
JSON_FILES = find("conf/").filter(fileType("json")),
- MARKDOWN_FILES_ARRAY = find("docs/").concat(ls(".")).filter(fileType("md")),
+ MARKDOWNLINT_IGNORED_FILES = fs.readFileSync(path.join(__dirname, ".markdownlintignore"), "utf-8").split("\n"),
+ MARKDOWN_FILES_ARRAY = find("docs/").concat(ls(".")).filter(fileType("md")).filter(file => !MARKDOWNLINT_IGNORED_FILES.includes(file)),
TEST_FILES = "\"tests/{bin,lib,tools}/**/*.js\"",
PERF_ESLINTRC = path.join(PERF_TMP_DIR, "eslintrc.yml"),
PERF_MULTIFILES_TARGET_DIR = path.join(PERF_TMP_DIR, "eslint"),
/**
* Generates a doc page with formatter result examples
- * @param {Object} formatterInfo Linting results from each formatter
- * @param {string} [prereleaseVersion] The version used for a prerelease. This
+ * @param {Object} formatterInfo Linting results from each formatter
+ * @param {string} [prereleaseVersion] The version used for a prerelease. This
* changes where the output is stored.
* @returns {void}
*/
*/
function generateRuleIndexPage() {
const outputFile = "../website/_data/rules.yml",
- categoryList = "conf/category-list.json",
- categoriesData = JSON.parse(cat(path.resolve(categoryList)));
+ ruleTypes = "conf/rule-type-list.json",
+ ruleTypesData = JSON.parse(cat(path.resolve(ruleTypes)));
RULE_FILES
.map(filename => [filename, path.basename(filename, ".js")])
const rule = require(path.resolve(filename));
if (rule.meta.deprecated) {
- categoriesData.deprecated.rules.push({
+ ruleTypesData.deprecated.rules.push({
name: basename,
replacedBy: rule.meta.replacedBy || []
});
name: basename,
description: rule.meta.docs.description,
recommended: rule.meta.docs.recommended || false,
- fixable: !!rule.meta.fixable
+ fixable: !!rule.meta.fixable,
+ hasSuggestions: !!rule.meta.hasSuggestions
},
- category = categoriesData.categories.find(c => c.name === rule.meta.docs.category);
+ ruleType = ruleTypesData.types.find(c => c.name === rule.meta.type);
- if (!category.rules) {
- category.rules = [];
+ if (!ruleType.rules) {
+ ruleType.rules = [];
}
- category.rules.push(output);
+ ruleType.rules.push(output);
}
});
// `.rules` will be `undefined` if all rules in category are deprecated.
- categoriesData.categories = categoriesData.categories.filter(category => !!category.rules);
+ ruleTypesData.types = ruleTypesData.types.filter(ruleType => !!ruleType.rules);
- const output = yaml.safeDump(categoriesData, { sortKeys: true });
+ const output = yaml.dump(ruleTypesData, { sortKeys: true });
output.to(outputFile);
}
* @private
*/
function lintMarkdown(files) {
- const config = yaml.safeLoad(fs.readFileSync(path.join(__dirname, "./.markdownlint.yml"), "utf8")),
+ const config = yaml.load(fs.readFileSync(path.join(__dirname, "./.markdownlint.yml"), "utf8")),
result = markdownlint.sync({
files,
config,
// Tasks
//------------------------------------------------------------------------------
-target.all = function() {
- target.test();
-};
-
target.lint = function([fix = false] = []) {
let errors = 0,
lastReturn;
echo("Running unit tests");
- lastReturn = exec(`${getBinFile("nyc")} -- ${MOCHA} -R progress -t ${MOCHA_TIMEOUT} -c ${TEST_FILES}`);
+ lastReturn = exec(`${getBinFile("nyc")} -- ${MOCHA} --forbid-only -R progress -t ${MOCHA_TIMEOUT} -c ${TEST_FILES}`);
if (lastReturn.code !== 0) {
errors++;
}
target.checkLicenses();
};
-target.docs = function() {
- echo("Generating documentation");
- exec(`${getBinFile("jsdoc")} -d jsdoc lib`);
- echo("Documentation has been output to /jsdoc");
-};
-
target.gensite = function(prereleaseVersion) {
echo("Generating eslint.org");
};
}
- const rules = require(".").linter.getRules();
+ const { Linter } = require(".");
+ const rules = new Linter().getRules();
const RECOMMENDED_TEXT = "\n\n(recommended) The `\"extends\": \"eslint:recommended\"` property in a configuration file enables this rule.";
const FIXABLE_TEXT = "\n\n(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.";
+ const HAS_SUGGESTIONS_TEXT = "\n\n(hasSuggestions) Some problems reported by this rule are manually fixable by editor [suggestions](../developer-guide/working-with-rules#providing-suggestions).";
// 4. Loop through all files in temporary directory
process.stdout.write("> Updating files (Steps 4-9): 0/... - ...\r");
tempFiles.forEach((filename, i) => {
if (test("-f", filename) && path.extname(filename) === ".md") {
- const rulesUrl = "https://github.com/eslint/eslint/tree/master/lib/rules/",
- docsUrl = "https://github.com/eslint/eslint/tree/master/docs/rules/",
+ const rulesUrl = "https://github.com/eslint/eslint/tree/HEAD/lib/rules/",
+ testsUrl = "https://github.com/eslint/eslint/tree/HEAD/tests/lib/rules/",
+ docsUrl = "https://github.com/eslint/eslint/tree/HEAD/docs/rules/",
baseName = path.basename(filename),
sourceBaseName = `${path.basename(filename, ".md")}.js`,
sourcePath = path.join("lib/rules", sourceBaseName),
const rule = rules.get(ruleName);
const isRecommended = rule && rule.meta.docs.recommended;
const isFixable = rule && rule.meta.fixable;
+ const hasSuggestions = rule && rule.meta.hasSuggestions;
// Incorporate the special portion into the documentation content
const textSplit = text.split("\n");
const ruleHeading = textSplit[0];
const ruleDocsContent = textSplit.slice(1).join("\n");
- text = `${ruleHeading}${isRecommended ? RECOMMENDED_TEXT : ""}${isFixable ? FIXABLE_TEXT : ""}\n${ruleDocsContent}`;
+ text = `${ruleHeading}${isRecommended ? RECOMMENDED_TEXT : ""}${isFixable ? FIXABLE_TEXT : ""}${hasSuggestions ? HAS_SUGGESTIONS_TEXT : ""}\n${ruleDocsContent}`;
title = `${ruleName} - Rules`;
if (rule && rule.meta) {
"---",
`title: ${title}`,
"layout: doc",
- `edit_link: https://github.com/eslint/eslint/edit/master/${filePath}`,
+ `edit_link: https://github.com/eslint/eslint/edit/main/${filePath}`,
ruleType,
"---",
"<!-- Note: No pull requests accepted for this file. See README.md in the root directory for details. -->",
text += "\n## Resources\n\n";
if (!removed) {
text += `* [Rule source](${rulesUrl}${sourceBaseName})\n`;
+ text += `* [Test source](${testsUrl}${sourceBaseName})\n`;
}
text += `* [Documentation source](${docsUrl}${baseName})\n`;
}
return idNewAtBeginningOfTitleRegExp.test(docText) || idOldAtEndOfTitleRegExp.test(docText);
}
+ /**
+ * Check if deprecated information is in rule code and READNE.md.
+ * @returns {boolean} true if present
+ * @private
+ */
+ function hasDeprecatedInfo() {
+ const ruleCode = cat(filename);
+ const deprecatedTagRegExp = /@deprecated in ESLint/u;
+ const docText = cat(docFilename);
+ const deprecatedInfoRegExp = /This rule was .+deprecated.+in ESLint/u;
+
+ return deprecatedTagRegExp.test(ruleCode) && deprecatedInfoRegExp.test(docText);
+ }
+
// check for docs
if (!test("-f", docFilename)) {
console.error("Missing documentation for rule %s", 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 eslint:recommended
- const recommended = require("./conf/eslint-recommended");
+ // check deprecated
+ if (ruleDef.meta.deprecated && !hasDeprecatedInfo()) {
+ console.error(`Missing deprecated information in ${basename} rule code or README.md. Please write @deprecated tag in code or 「This rule was deprecated in ESLint ...」 in README.md.`);
+ errors++;
+ }
+
+ // check eslint:recommended
+ const recommended = require("./conf/eslint-recommended");
- if (ruleDef) {
if (ruleDef.meta.docs.recommended) {
if (recommended.rules[basename] !== "error") {
console.error(`Missing rule from eslint:recommended (./conf/eslint-recommended.js): ${basename}. If you just made a rule recommended then add an entry for it in this file.`);
content.join("\n").to(PERF_ESLINTRC);
}
+/**
+ * @callback TimeCallback
+ * @param {?int[]} results
+ * @returns {void}
+ */
+
/**
* Calculates the time for each run for performance
* @param {string} cmd cmd
* @param {int} runs Total number of runs to do
* @param {int} runNumber Current run number
* @param {int[]} results Collection results from each run
- * @param {Function} cb Function to call when everything is done
- * @returns {int[]} calls the cb with all the results
+ * @param {TimeCallback} cb Function to call when everything is done
+ * @returns {void} calls the cb with all the results
* @private
*/
function time(cmd, runs, runNumber, results, cb) {
// Count test target files.
const count = glob.sync(
- process.platform === "win32"
- ? PERF_MULTIFILES_TARGETS.slice(2).replace(/\\/gu, "/")
- : PERF_MULTIFILES_TARGETS
+ (
+ process.platform === "win32"
+ ? PERF_MULTIFILES_TARGETS.replace(/\\/gu, "/")
+ : PERF_MULTIFILES_TARGETS
+ )
+ .slice(1, -1) // strip quotes
).length;
runPerformanceTest(
## <a name="installation-and-usage"></a>Installation and Usage
-Prerequisites: [Node.js](https://nodejs.org/) (`^10.12.0`, or `>=12.0.0`) built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.)
+Prerequisites: [Node.js](https://nodejs.org/) (`^12.22.0`, `^14.17.0`, or `>=16.0.0`) built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.)
You can install ESLint using npm:
### What ECMAScript versions does ESLint support?
-ESLint has full support for ECMAScript 3, 5 (default), 2015, 2016, 2017, 2018, 2019, and 2020. You can set your desired ECMAScript syntax (and other settings, like global variables or your target environments) through [configuration](https://eslint.org/docs/user-guide/configuring).
+ESLint has full support for ECMAScript 3, 5 (default), 2015, 2016, 2017, 2018, 2019, 2020, 2021 and 2022. You can set your desired ECMAScript syntax (and other settings, like global variables or your target environments) through [configuration](https://eslint.org/docs/user-guide/configuring).
### What about experimental features?
These folks keep the project moving and are resources for help.
<!-- NOTE: This section is autogenerated. Do not manually edit.-->
+
+
+
<!--teamstart-->
### Technical Steering Committee (TSC)
</td><td align="center" valign="top" width="11%">
<a href="https://github.com/aladdin-add">
<img src="https://github.com/aladdin-add.png?s=75" width="75" height="75"><br />
-薛定谔的猫
+唯然
</a>
</td></tr></tbody></table>
The people who review and fix bugs and help triage issues.
<table><tbody><tr><td align="center" valign="top" width="11%">
+<a href="https://github.com/brettz9">
+<img src="https://github.com/brettz9.png?s=75" width="75" height="75"><br />
+Brett Zamir
+</a>
+</td><td align="center" valign="top" width="11%">
+<a href="https://github.com/bmish">
+<img src="https://github.com/bmish.png?s=75" width="75" height="75"><br />
+Bryan Mishkin
+</a>
+</td><td align="center" valign="top" width="11%">
<a href="https://github.com/g-plane">
<img src="https://github.com/g-plane.png?s=75" width="75" height="75"><br />
Pig Fang
<img src="https://github.com/yeonjuan.png?s=75" width="75" height="75"><br />
YeonJuan
</a>
+</td><td align="center" valign="top" width="11%">
+<a href="https://github.com/snitin315">
+<img src="https://github.com/snitin315.png?s=75" width="75" height="75"><br />
+Nitin Kumar
+</a>
</td></tr></tbody></table>
<!--teamend-->
+
+
+
## <a name="sponsors"></a>Sponsors
The following companies, organizations, and individuals support ESLint's ongoing maintenance and development. [Become a Sponsor](https://opencollective.com/eslint) to get your logo on our README and website.
<!--sponsorsstart-->
<h3>Platinum Sponsors</h3>
<p><a href="https://automattic.com"><img src="https://images.opencollective.com/photomatt/d0ef3e1/logo.png" alt="Automattic" height="undefined"></a></p><h3>Gold Sponsors</h3>
-<p><a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" height="96"></a> <a href="https://google.com/chrome"><img src="https://images.opencollective.com/chrome/dc55bd4/logo.png" alt="Chrome's Web Framework & Tools Performance Fund" height="96"></a> <a href="https://www.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a> <a href="https://substack.com/"><img src="https://avatars.githubusercontent.com/u/53023767?v=4" alt="Substack" height="96"></a></p><h3>Silver Sponsors</h3>
-<p><a href="https://retool.com/"><img src="https://images.opencollective.com/retool/98ea68e/logo.png" alt="Retool" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a></p><h3>Bronze Sponsors</h3>
-<p><a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="null"><img src="https://images.opencollective.com/bugsnag-stability-monitoring/c2cef36/logo.png" alt="Bugsnag Stability Monitoring" height="32"></a> <a href="https://mixpanel.com"><img src="https://images.opencollective.com/mixpanel/cd682f7/logo.png" alt="Mixpanel" height="32"></a> <a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8: free icons, photos, illustrations, and music" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/7e3d9a9/logo.png" alt="Discord" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://www.firesticktricks.com"><img src="https://images.opencollective.com/fire-stick-tricks/b8fbe2c/logo.png" alt="Fire Stick Tricks" height="32"></a></p>
+<p><a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" height="96"></a> <a href="https://google.com/chrome"><img src="https://images.opencollective.com/chrome/dc55bd4/logo.png" alt="Chrome's Web Framework & Tools Performance Fund" height="96"></a> <a href="https://www.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a> <a href="https://coinbase.com"><img src="https://avatars.githubusercontent.com/u/1885080?v=4" alt="Coinbase" height="96"></a> <a href="https://americanexpress.io"><img src="https://avatars.githubusercontent.com/u/3853301?v=4" alt="American Express" height="96"></a> <a href="https://substack.com/"><img src="https://avatars.githubusercontent.com/u/53023767?v=4" alt="Substack" height="96"></a></p><h3>Silver Sponsors</h3>
+<p><a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a></p><h3>Bronze Sponsors</h3>
+<p><a href="https://launchdarkly.com"><img src="https://images.opencollective.com/launchdarkly/574bb9e/logo.png" alt="launchdarkly" height="32"></a> <a href="https://troypoint.com"><img src="https://images.opencollective.com/troypoint/080f96f/avatar.png" alt="TROYPOINT" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8: free icons, photos, illustrations, and music" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://www.firesticktricks.com"><img src="https://images.opencollective.com/fire-stick-tricks/b8fbe2c/logo.png" alt="Fire Stick Tricks" height="32"></a> <a href="https://www.practiceignition.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Practice Ignition" height="32"></a></p>
<!--sponsorsend-->
## <a name="technology-sponsors"></a>Technology Sponsors
* @author Nicholas C. Zakas
*/
-/* eslint no-console:off */
+/* eslint no-console:off -- CLI */
"use strict";
+++ /dev/null
-{
- "categories": [
- { "name": "Possible Errors", "description": "These rules relate to possible syntax or logic errors in JavaScript code:" },
- { "name": "Best Practices", "description": "These rules relate to better ways of doing things to help you avoid problems:" },
- { "name": "Strict Mode", "description": "These rules relate to strict mode directives:" },
- { "name": "Variables", "description": "These rules relate to variable declarations:" },
- { "name": "Stylistic Issues", "description": "These rules relate to style guidelines, and are therefore quite subjective:" },
- { "name": "ECMAScript 6", "description": "These rules relate to ES6, also known as ES2015:" }
- ],
- "deprecated": {
- "name": "Deprecated",
- "description": "These rules have been deprecated in accordance with the <a href=\"/docs/user-guide/rule-deprecation\">deprecation policy</a>, and replaced by newer rules:",
- "rules": []
- },
- "removed": {
- "name": "Removed",
- "description": "These rules from older versions of ESLint (before the <a href=\"/docs/user-guide/rule-deprecation\">deprecation policy</a> existed) have been replaced by newer rules:",
- "rules": [
- { "removed": "generator-star", "replacedBy": ["generator-star-spacing"] },
- { "removed": "global-strict", "replacedBy": ["strict"] },
- { "removed": "no-arrow-condition", "replacedBy": ["no-confusing-arrow", "no-constant-condition"] },
- { "removed": "no-comma-dangle", "replacedBy": ["comma-dangle"] },
- { "removed": "no-empty-class", "replacedBy": ["no-empty-character-class"] },
- { "removed": "no-empty-label", "replacedBy": ["no-labels"] },
- { "removed": "no-extra-strict", "replacedBy": ["strict"] },
- { "removed": "no-reserved-keys", "replacedBy": ["quote-props"] },
- { "removed": "no-space-before-semi", "replacedBy": ["semi-spacing"] },
- { "removed": "no-wrap-func", "replacedBy": ["no-extra-parens"] },
- { "removed": "space-after-function-name", "replacedBy": ["space-before-function-paren"] },
- { "removed": "space-after-keywords", "replacedBy": ["keyword-spacing"] },
- { "removed": "space-before-function-parentheses", "replacedBy": ["space-before-function-paren"] },
- { "removed": "space-before-keywords", "replacedBy": ["keyword-spacing"] },
- { "removed": "space-in-brackets", "replacedBy": ["object-curly-spacing", "array-bracket-spacing"] },
- { "removed": "space-return-throw-case", "replacedBy": ["keyword-spacing"] },
- { "removed": "space-unary-word-ops", "replacedBy": ["space-unary-ops"] },
- { "removed": "spaced-line-comment", "replacedBy": ["spaced-comment"] }
- ]
- }
-}
--- /dev/null
+{
+ "types": [
+ { "name": "problem", "displayName": "Possible Problems", "description": "These rules relate to possible logic errors in code:" },
+ { "name": "suggestion", "displayName": "Suggestions", "description": "These rules suggest alternate ways of doing things:" },
+ { "name": "layout", "displayName": "Layout & Formatting", "description": "These rules care about how the code looks rather than how it executes:" }
+ ],
+ "deprecated": {
+ "name": "Deprecated",
+ "description": "These rules have been deprecated in accordance with the <a href=\"/docs/user-guide/rule-deprecation\">deprecation policy</a>, and replaced by newer rules:",
+ "rules": []
+ },
+ "removed": {
+ "name": "Removed",
+ "description": "These rules from older versions of ESLint (before the <a href=\"/docs/user-guide/rule-deprecation\">deprecation policy</a> existed) have been replaced by newer rules:",
+ "rules": [
+ { "removed": "generator-star", "replacedBy": ["generator-star-spacing"] },
+ { "removed": "global-strict", "replacedBy": ["strict"] },
+ { "removed": "no-arrow-condition", "replacedBy": ["no-confusing-arrow", "no-constant-condition"] },
+ { "removed": "no-comma-dangle", "replacedBy": ["comma-dangle"] },
+ { "removed": "no-empty-class", "replacedBy": ["no-empty-character-class"] },
+ { "removed": "no-empty-label", "replacedBy": ["no-labels"] },
+ { "removed": "no-extra-strict", "replacedBy": ["strict"] },
+ { "removed": "no-reserved-keys", "replacedBy": ["quote-props"] },
+ { "removed": "no-space-before-semi", "replacedBy": ["semi-spacing"] },
+ { "removed": "no-wrap-func", "replacedBy": ["no-extra-parens"] },
+ { "removed": "space-after-function-name", "replacedBy": ["space-before-function-paren"] },
+ { "removed": "space-after-keywords", "replacedBy": ["keyword-spacing"] },
+ { "removed": "space-before-function-parentheses", "replacedBy": ["space-before-function-paren"] },
+ { "removed": "space-before-keywords", "replacedBy": ["keyword-spacing"] },
+ { "removed": "space-in-brackets", "replacedBy": ["object-curly-spacing", "array-bracket-spacing"] },
+ { "removed": "space-return-throw-case", "replacedBy": ["keyword-spacing"] },
+ { "removed": "space-unary-word-ops", "replacedBy": ["space-unary-ops"] },
+ { "removed": "spaced-line-comment", "replacedBy": ["spaced-comment"] }
+ ]
+ }
+}
## Section 5: [Working with Plugins](working-with-plugins.md)
-You've developed library-specific rules for ESLint and you want to share it with the community. You can publish an ESLint plugin on npm.
+You've developed library-specific rules for ESLint and you want to share them with the community. You can publish an ESLint plugin on npm.
## Section 6: [Working with Custom Parsers](working-with-custom-parsers.md)
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`, `CLIEngine`, `RuleTester`, and `SourceCode`.
+* `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/init/` - this module contains `--init` functionality that set up a configuration file for end users.
* `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.
# Code Conventions
-Programming language style guides are important for the long-term maintainability of software. This guide is based on the [Code Conventions for the Java Programming Language](https://java.sun.com/docs/codeconv/) and [Douglas Crockford's Code Conventions for the JavaScript Programming Language](http://javascript.crockford.com/code.html). Modifications have been made due to my personal experience and preferences.
+Code conventions for ESLint are determined by
+[eslint-config-eslint](https://www.npmjs.com/package/eslint-config-eslint).
-## File Format
-
-Each file has this same basic format:
-
-```js
-/**
- * @fileoverview Description of the file
- * @author Your Name
- */
-
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-// require() statements
-
-//------------------------------------------------------------------------------
-// Helpers
-//------------------------------------------------------------------------------
-
-// private methods/data
-
-//------------------------------------------------------------------------------
-// Public Interface
-//------------------------------------------------------------------------------
-
-// exported objects/methods
-module.exports = {
-
-};
-```
-
-The `@author` field gives you credit for having created the file.
-
-## Indentation
-
-Each indentation level is made up of four spaces. Do not use tabs.
-
- // Good
- if (true) {
- doSomething();
- }
-
-## Primitive Literals
-
-Strings should always use double quotes (never single quotes) and should always appear on a single line. Never use a slash to create a new line in a string.
-
- // Good
- var name = "Nicholas";
-
- // Bad: Single quotes
- var name = 'Nicholas';
-
- // Bad: Wrapping to second line
- var longString = "Here's the story, of a man \
- named Brady.";
-
-Numbers should be written as decimal integers, e-notation integers, hexadecimal integers or floating-point decimals with at least one digit before and one digit after the decimal point. Never use octal literals.
-
- // Good
- var count = 10;
-
- // Good
- var price = 10.0;
- var price = 10.00;
-
- // Good
- var num = 0xA2;
-
- // Good
- var num = 1e23;
-
- // Bad: Hanging decimal point
- var price = 10.;
-
- // Bad: Leading decimal point
- var price = .1;
-
- // Bad: Octal (base 8) is deprecated
- var num = 010;
-
-The special value `null` should be used only in the following situations:
-
-1. To initialize a variable that may later be assign an object value.
-1. To compare against an initialized variable that may or may not have an object value.
-1. To pass into a function where an object is expected.
-1. To return from a function where an object is expected.
-
-Examples:
-
- // Good
- var person = null;
-
- // Good
- function getPerson() {
- if (condition) {
- return new Person("Nicholas");
- } else {
- return null;
- }
- }
-
- // Good
- var person = getPerson();
- if (person !== null){
- doSomething();
- }
-
- // Bad: Testing against uninitialized variable
- var person;
- if (person != null){
- doSomething();
- }
-
- // Bad: Testing to see if an argument was passed
- function doSomething(arg1, arg2, arg3, arg4){
- if (arg4 != null){
- doSomethingElse();
- }
- }
-
-Never use the special value `undefined`. To see if a variable has been defined, use the `typeof` operator:
-
- // Good
- if (typeof variable == "undefined") {
- // do something
- }
-
- // Bad: Using undefined literal
- if (variable == undefined) {
- // do something
- }
-
-## Operator Spacing
-
-Operators with two operands must be preceded and followed by a single space to make the expression clear. Operators include assignments and logical operators.
-
- // Good
- var found = (values[i] === item);
-
- // Good
- if (found && (count > 10)) {
- doSomething();
- }
-
- // Good
- for (i = 0; i < count; i++) {
- process(i);
- }
-
- // Bad: Missing spaces
- var found = (values[i]===item);
-
- // Bad: Missing spaces
- if (found&&(count>10)) {
- doSomething();
- }
-
- // Bad: Missing spaces
- for (i=0; i<count; i++) {
- process(i);
- }
-
-## Parentheses Spacing
-
-When parentheses are used, there should be no whitespace immediately after the opening paren or immediately before the closing paren.
-
- // Good
- var found = (values[i] === item);
-
- // Good
- if (found && (count > 10)) {
- doSomething();
- }
-
- // Good
- for (i = 0; i < count; i++) {
- process(i);
- }
-
- // Bad: Extra space after opening paren
- var found = ( values[i] === item);
-
- // Bad: Extra space before closing paren
- if (found && (count > 10) ) {
- doSomething();
- }
-
- // Bad: Extra space around argument
- for (i = 0; i < count; i++) {
- process( i );
- }
-
-## Object Literals
-
-Object literals should have the following format:
-
-* The opening brace should be on the same line as the containing statement.
-* Each property-value pair should be indented one level with the first property appearing on the next line after the opening brace.
-* Each property-value pair should have an unquoted property name, followed by a colon (no space preceding it), followed by the value.
-* If the value is a function, it should wrap under the property name and should have a blank line both before and after the function.
-* Additional empty lines may be inserted to group related properties or otherwise improve readability.
-* The closing brace should be on a separate line.
-
-Examples:
-
- // Good
- var object = {
-
- key1: value1,
- key2: value2,
-
- func: function() {
- // do something
- },
-
- key3: value3
- };
-
- // Bad: Improper indentation
- var object = {
- key1: value1,
- key2: value2
- };
-
- // Bad: Missing blank lines around function
- var object = {
-
- key1: value1,
- key2: value2,
- func: function() {
- // do something
- },
- key3: value3
- };
-
-When an object literal is passed to a function, the opening brace should be on the same line as if the value is a variable. All other formatting rules from above still apply.
-
- // Good
- doSomething({
- key1: value1,
- key2: value2
- });
-
- // Bad: All on one line
- doSomething({ key1: value1, key2: value2 });
-
-## Comments
-
-Make frequent use of comments to aid others in understanding your code. Use comments when:
-
-* Code is difficult to understand.
-* The code might be mistaken for an error.
-* Browser-specific code is necessary but not obvious.
-* Documentation generation is necessary for an object, method, or property (use appropriate documentation comments).
-
-### Single-Line Comments
-
-Single-line comments should be used to document one line of code or a group of related lines of code. A single-line comment may be used in three ways:
-
-1. On a separate line, describing the code beneath it.
-1. At the end of a line, describing the code before it.
-1. On multiple lines, to comment out sections of code.
-
-When on a separate line, a single-line comment should be at the same indentation level as the code it describes and be preceded by a single line. Never use multiple single-line comments on consecutive lines, use a multi-line comment instead.
-
- // Good
- if (condition){
-
- // if you made it here, then all security checks passed
- allowed();
- }
-
- // Bad: No empty line preceding comment
- if (condition){
- // if you made it here, then all security checks passed
- allowed();
- }
-
- // Bad: Wrong indentation
- if (condition){
-
- // if you made it here, then all security checks passed
- allowed();
- }
-
- // Bad: This should be a multi-line comment
- // This next piece of code is quite difficult, so let me explain.
- // What you want to do is determine if the condition is true
- // and only then allow the user in. The condition is calculated
- // from several different functions and may change during the
- // lifetime of the session.
- if (condition){
- // if you made it here, then all security checks passed
- allowed();
- }
-
-For single-line comments at the end of a line, ensure there is at least one indentation level between the end of the code and the beginning of the comment:
-
- // Good
- var result = something + somethingElse; // somethingElse will never be null
-
- // Bad: Not enough space between code and comment
- var result = something + somethingElse;// somethingElse will never be null
-
-The only acceptable time to have multiple single-line comments on successive lines is to comment out large sections of code. Multi-line comments should not be used for this purpose.
-
- // Good
- // if (condition){
- // doSomething();
- // thenDoSomethingElse();
- // }
-
-### Multi-Line Comments
-
-Multi-line comments should be used to document code that requires more explanation. Each multi-line comment should have at least three lines:
-
-1. The first line contains only the `/*` comment opening. No further text is allowed on this line.
-1. The next line(s) have a `*` aligned with the `*` in the first line. Text is allowed on these lines.
-1. The last line has the `*/` comment opening aligned with the preceding lines. No other text is allowed on this line.
-
-The first line of multi-comments should be indented to the same level as the code it describes. Each subsequent line should have the same indentation plus one space (for proper alignment of the `*` characters). Each multi-line comment should be preceded by one empty line.
-
- // Good
- if (condition){
-
- /*
- * if you made it here,
- * then all security checks passed
- */
- allowed();
- }
-
- // Bad: No empty line preceding comment
- if (condition){
- /*
- * if you made it here,
- * then all security checks passed
- */
- allowed();
- }
-
- // Bad: Missing a space after asterisk
- if (condition){
-
- /*
- *if you made it here,
- *then all security checks passed
- */
- allowed();
- }
-
- // Bad: Wrong indentation
- if (condition){
-
- /*
- * if you made it here,
- * then all security checks passed
- */
- allowed();
- }
-
- // Bad: Don't use multi-line comments for trailing comments
- var result = something + somethingElse; /*somethingElse will never be null*/
-
-### Comment Annotations
-
-Comments may be used to annotate pieces of code with additional information. These annotations take the form of a single word followed by a colon. The acceptable annotations are:
-
-* `TODO` - indicates that the code is not yet complete. Information about the next steps should be included.
-* `HACK` - indicates that the code is using a shortcut. Information about why the hack is being used should be included. This may also indicate that it would be nice to come up with a better way to solve the problem.
-* `XXX` - indicates that the code is problematic and should be fixed as soon as possible.
-* `FIXME` - indicates that the code is problematic and should be fixed soon. Less important than `XXX`.
-* `REVIEW` - indicates that the code needs to be reviewed for potential changes.
-
-These annotations may be used with either single-line or multi-line comments and should follow the same formatting rules as the general comment type. Examples:
-
- // Good
- // TODO: I'd like to find a way to make this faster
- doSomething();
-
- // Good
- /*
- * HACK: Have to do this for IE. I plan on revisiting in
- * the future when I have more time. This probably should
- * get replaced before v1.2.
- */
- if (document.all) {
- doSomething();
- }
-
- // Good
- // REVIEW: Is there a better way to do this?
- if (document.all) {
- doSomething();
- }
-
- // Bad: Annotation spacing is incorrect
- // TODO : I'd like to find a way to make this faster
- doSomething();
-
- // Bad: Comment should be at the same indentation as code
- // REVIEW: Is there a better way to do this?
- if (document.all) {
- doSomething();
- }
-
-
-## Variable Declarations
-
-All variables should be declared before they are used. Variable declarations should take place at the beginning of a function using a single `var` statement with one variable per line. All lines after the first should be indented one level so the variable names line up. Variables should be initialized when declared if applicable and the equals operator should be at a consistent indentation level. Initialized variables should come first followed by uninitialized variables.
-
- // Good
- var count = 10,
- name = "Nicholas",
- found = false,
- empty;
-
- // Bad: Improper initialization alignment
- var count = 10,
- name = "Nicholas",
- found= false,
- empty;
-
- // Bad: Incorrect indentation
- var count = 10,
- name = "Nicholas",
- found = false,
- empty;
-
- // Bad: Multiple declarations on one line
- var count = 10, name = "Nicholas",
- found = false, empty;
-
- // Bad: Uninitialized variables first
- var empty,
- count = 10,
- name = "Nicholas",
- found = false;
-
- // Bad: Multiple var statements
- var count = 10,
- name = "Nicholas";
-
- var found = false,
- empty;
-
-Always declare variables. Implied globals should not be used.
-
-## Function Declarations
-
-Functions should be declared before they are used. When a function is not a method (not attached to an object) it should be defined using function declaration format (not function expression format nor using the `Function` constructor). There should be no space between the function name and the opening parentheses. There should be one space between the closing parentheses and the right brace. The right brace should be on the same line as the `function` keyword. There should be no space after the opening parentheses or before the closing parentheses. Named arguments should have a space after the comma but not before it. The function body should be indented one level.
-
- // Good
- function doSomething(arg1, arg2) {
- return arg1 + arg2;
- }
-
- // Bad: Improper spacing of first line
- function doSomething (arg1, arg2){
- return arg1 + arg2;
- }
-
- // Bad: Function expression
- var doSomething = function(arg1, arg2) {
- return arg1 + arg2;
- };
-
- // Bad: Left brace on wrong line
- function doSomething(arg1, arg2)
- {
- return arg1 + arg2;
- }
-
- // Bad: Using Function constructor
- var doSomething = new Function("arg1", "arg2", "return arg1 + arg2");
-
-Functions declared inside of other functions should be declared immediately after the `var` statement.
-
- // Good
- function outer() {
-
- var count = 10,
- name = "Nicholas",
- found = false,
- empty;
-
- function inner() {
- // code
- }
-
- // code that uses inner()
- }
-
- // Bad: Inner function declared before variables
- function outer() {
-
- function inner() {
- // code
- }
-
- var count = 10,
- name = "Nicholas",
- found = false,
- empty;
-
- // code that uses inner()
- }
-
-Anonymous functions may be used for assignment of object methods or as arguments to other functions. There should be no space between the `function` keyword and the opening parentheses.
-
- // Good
- object.method = function() {
- // code
- };
-
- // Bad: Incorrect spacing
- object.method = function () {
- // code
- };
-
-Immediately-invoked functions should surround the entire function call with parentheses.
-
- // Good
- var value = (function() {
-
- // function body
-
- return {
- message: "Hi"
- }
- }());
-
- // Bad: No parentheses around function call
- var value = function() {
-
- // function body
-
- return {
- message: "Hi"
- }
- }();
-
- // Bad: Improper parentheses placement
- var value = (function() {
-
- // function body
-
- return {
- message: "Hi"
- }
- })();
-
-## Naming
-
-Care should be taken to name variables and functions properly. Names should be limited to alphanumeric characters and, in some cases, the underscore character. Do not use the dollar sign (`$`) or back slash (`\`) characters in any names.
-
-Variable names should be formatted in camel case with the first letter being lowercase and the first letter of each subsequent word being uppercase. The first word of a variable name should be a noun (not a verb) to avoid confusion with functions. Do not use underscore for variable names.
-
- // Good
- var accountNumber = "8401-1";
-
- // Bad: Begins with uppercase letter
- var AccountNumber = "8401-1";
-
- // Bad: Begins with verb
- var getAccountNumber = "8401-1";
-
- // Bad: Uses underscore
- var account_number = "8401-1";
-
-Function names should also be formatted using camel case. The first word of a function name should be a verb (not a noun) to avoid confusion with variables. Do not use underscore for function names.
-
- // Good
- function doSomething() {
- // code
- }
-
- // Bad: Begins with uppercase letter
- function DoSomething() {
- // code
- }
-
- // Bad: Begins with noun
- function car() {
- // code
- }
-
- // Bad: Uses underscores
- function do_something() {
- // code
- }
-
-Constructor functions, those functions used with the `new` operator to create new objects, should be formatted in camel case but must begin with an uppercase letter. Constructor function names should begin with a non-verb because `new` is the action of creating an object instance.
-
- // Good
- function MyObject() {
- // code
- }
-
- // Bad: Begins with lowercase letter
- function myObject() {
- // code
- }
-
- // Bad: Uses underscores
- function My_Object() {
- // code
- }
-
- // Bad: Begins with verb
- function getMyObject() {
- // code
- }
-
-Variables that act as constants (values that won't be changed) should be formatted using all uppercase letters with words separated by a single underscore.
-
- // Good
- var TOTAL_COUNT = 10;
-
- // Bad: Camel case
- var totalCount = 10;
-
- // Bad: Mixed case
- var total_COUNT = 10;
-
-Object properties follow the same naming conventions as variables. Object methods follow the same naming conventions as functions. If a property or method is meant to be private, then it should be prefixed with an underscore character.
-
- // Good
- var object = {
- _count: 10,
-
- _getCount: function () {
- return this._count;
- }
- };
-
-## Strict Mode
-
-Strict mode should be used in all modules, specified below the file overview comment and above everything else:
-
- // Bad: Strict mode in functions
- function doSomething() {
- "use strict";
-
- // code
- }
-
- // Bad: Strict mode in global scope and redundant strict mode directive in function
- "use strict"; // This one is good
-
- function doSomething() {
- "use strict"; // This one is bad
-
- // code
- }
-
- // Good: Global strict mode
- "use strict";
-
- function doSomething() {
- // no "use strict" here
-
- // code
- }
-
-## Assignments
-
-When assigning a value to a variable, use parentheses around a right-side expression that contains a comparison.
-
- // Good
- var flag = (i < count);
-
- // Bad: Missing parentheses
- var flag = i < count;
-
-## Equality Operators
-
-Use `===` and `!==` instead of `==` and `!=`. This avoids type coercion errors.
-
- // Good
- var same = (a === b);
-
- // Bad: Using ==
- var same = (a == b);
-
-## Ternary Operator
-
-The ternary operator should be used only for assigning values conditionally and never as a shortcut for an `if` statement.
-
- // Good
- var value = condition ? value1 : value2;
-
- // Bad: no assignment, should be an if statement
- condition ? doSomething() : doSomethingElse();
-
-## Statements
-
-### Simple Statements
-
-Each line should contain at most one statement. All simple statements should end with a semicolon (`;`).
-
- // Good
- count++;
- a = b;
-
- // Bad: Multiple statements on one line
- count++; a = b;
-
-### return Statement
-
-A return statement with a value should not use parentheses unless they make the return value more obvious in some way. Example:
-
- return;
-
- return collection.size();
-
- return (size > 0 ? size : defaultSize);
-
-### Compound Statements
-
-Compound statements are lists of statements enclosed inside of braces.
-
-* The enclosed statements should be indented one more level than the compound statement.
-* The opening brace should be at the end of the line that begins the compound statement; the closing brace should begin a line and be indented to the beginning of the compound statement.
-* Braces are used around all statements, even single statements, when they are part of a control structure, such as a `if` or `for` statement. This makes it easier to add statements without accidentally introducing bugs due to forgetting to add braces.
-* The statement beginning keyword, such as `if`, should be followed by one space and the opening brace should be preceded by a space.
-
-### if Statement
-
-The `if` class of statements should have the following form:
-
- if (condition) {
- statements
- }
-
- if (condition) {
- statements
- } else {
- statements
- }
-
- if (condition) {
- statements
- } else if (condition) {
- statements
- } else {
- statements
- }
-
-It is never permissible to omit the braces in any part of an `if` statement.
-
- // Good
- if (condition) {
- doSomething();
- }
-
- // Bad: Improper spacing
- if(condition){
- doSomething();
- }
-
- // Bad: Missing braces
- if (condition)
- doSomething();
-
- // Bad: All on one line
- if (condition) { doSomething(); }
-
- // Bad: All on one line without braces
- if (condition) doSomething();
-
-### for Statement
-
-The `for` class of statements should have the following form:
-
- for (initialization; condition; update) {
- statements
- }
-
- for (variable in object) {
- statements
- }
-
-Variables should not be declared in the initialization section of a `for` statement.
-
- // Good
- var i,
- len;
-
- for (i=0, len=10; i < len; i++) {
- // code
- }
-
- // Bad: Variables declared during initialization
- for (var i=0, len=10; i < len; i++) {
- // code
- }
-
- // Bad: Variables declared during initialization
- for (var prop in object) {
- // code
- }
-
-When using a `for-in` statement, double-check whether or not you need to use `hasOwnProperty()` to filter out object members.
-
-### while Statement
-
-The `while` class of statements should have the following form:
-
- while (condition) {
- statements
- }
-
-### do Statement
-
-The `do` class of statements should have the following form:
-
- do {
- statements
- } while (condition);
-
-Note the use of a semicolon as the final part of this statement. There should be a space before and after the `while` keyword.
-
-### switch Statement
-
-The `switch` class of statements should have the following form:
-
- switch (expression) {
- case expression:
- statements
-
- default:
- statements
- }
-
-Each `case` is indented one level under the `switch`. Each `case` after the first, including `default`, should be preceded by a single empty line.
-
-Each group of statements (except the default) should end with `break`, `return`, `throw`, or a comment indicating fall through.
-
- // Good
- switch (value) {
- case 1:
- /* falls through */
-
- case 2:
- doSomething();
- break;
-
- case 3:
- return true;
-
- default:
- throw new Error("This shouldn't happen.);
- }
-
-If a `switch` doesn't have a `default` case, then it should be indicated with a comment.
-
- // Good
- switch (value) {
- case 1:
- /*falls through*/
-
- case 2:
- doSomething();
- break;
-
- case 3:
- return true;
-
- // no default
- }
-
-### try Statement
-
-The `try` class of statements should have the following form:
-
- try {
- statements
- } catch (variable) {
- statements
- }
-
- try {
- statements
- } catch (variable) {
- statements
- } finally {
- statements
- }
-
-## Whitespace
-
-Blank lines improve readability by setting off sections of code that are logically related.
-
-Two blank lines should always be used in the following circumstances:
-
-* Between sections of a source file
-* Between class and interface definitions
-
-One blank line should always be used in the following circumstances:
-
-* Between methods
-* Between the local variables in a method and its first statement
-* Before a multi-line or single-line comment
-* Between logical sections inside a method to improve readability
-
-Blank spaces should be used in the following circumstances:
-
-* A keyword followed by a parenthesis should be separated by a space.
-* A blank space should appear after commas in argument lists.
-* All binary operators except dot (`.`) should be separated from their operands by spaces. Blank spaces should never separate unary operators such as unary minus, increment (`++`), and decrement (`--`) from their operands.
-* The expressions in a `for` statement should be separated by blank spaces. Blank spaces should only be used after semicolons, not before.
-
-## Things to Avoid
-
-* Never use the primitive wrapper types, such as `String`, to create new objects.
-* Never use `eval()`.
-* Never use the `with` statement. This statement isn't available in strict mode and likely won't be available in future ECMAScript editions.
+The rationales for the specific rules in use can be found by looking to the
+project documentation for any given rule. If the rule is one of our own, see
+our own [rule documentation](https://eslint.org/docs/rules/) and otherwise, see
+the documentation of the plugin in which the rule can be found.
`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.
```
See Also:
-[no-unreachable](https://github.com/eslint/eslint/blob/master/lib/rules/no-unreachable.js),
-[no-fallthrough](https://github.com/eslint/eslint/blob/master/lib/rules/no-fallthrough.js),
-[consistent-return](https://github.com/eslint/eslint/blob/master/lib/rules/consistent-return.js)
+[no-unreachable](https://github.com/eslint/eslint/blob/HEAD/lib/rules/no-unreachable.js),
+[no-fallthrough](https://github.com/eslint/eslint/blob/HEAD/lib/rules/no-fallthrough.js),
+[consistent-return](https://github.com/eslint/eslint/blob/HEAD/lib/rules/consistent-return.js)
### To check state of a code path
```
See Also:
-[constructor-super](https://github.com/eslint/eslint/blob/master/lib/rules/constructor-super.js),
-[no-this-before-super](https://github.com/eslint/eslint/blob/master/lib/rules/no-this-before-super.js)
+[constructor-super](https://github.com/eslint/eslint/blob/HEAD/lib/rules/constructor-super.js),
+[no-this-before-super](https://github.com/eslint/eslint/blob/HEAD/lib/rules/no-this-before-super.js)
## Code Path Examples
-# Code Path Analysis Details
-
-ESLint's rules can use code paths.
-The code path is execution routes of programs.
-It forks/joins at such as `if` statements.
-
-```js
-if (a && b) {
- foo();
-}
-bar();
-```
-
-![Code Path Example](./helo.svg)
-
-## Objects
-
-Program is expressed with several code paths.
-A code path is expressed with objects of two kinds: `CodePath` and `CodePathSegment`.
-
-### `CodePath`
-
-`CodePath` expresses whole of one code path.
-This object exists for each function and the global.
-This has references of both the initial segment and the final segments of a code path.
-
-`CodePath` has the following properties:
-
-* `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each code path.
-* `initialSegment` (`CodePathSegment`) - The initial segment of this code path.
-* `finalSegments` (`CodePathSegment[]`) - The final segments which includes both returned and thrown.
-* `returnedSegments` (`CodePathSegment[]`) - The final segments which includes only returned.
-* `thrownSegments` (`CodePathSegment[]`) - The final segments which includes only thrown.
-* `currentSegments` (`CodePathSegment[]`) - Segments of the current position.
-* `upper` (`CodePath|null`) - The code path of the upper function/global scope.
-* `childCodePaths` (`CodePath[]`) - Code paths of functions this code path contains.
-
-### `CodePathSegment`
-
-`CodePathSegment` is a part of a code path.
-A code path is expressed with plural `CodePathSegment` objects, it's similar to doubly linked list.
-Difference from doubly linked list is what there are forking and merging (the next/prev are plural).
-
-`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`.
-
-## Events
-
-There are five events related to code paths, and you can define event handlers in rules.
-
-```js
-module.exports = function(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": function(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": function(codePath, node) {
- // do something with codePath
- },
-
- /**
- * This is called when a 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": function(segment, node) {
- // do something with segment
- },
-
- /**
- * This is called when a code path segment was leaved.
- * In this time, the segment does not have the next segments yet.
- *
- * @param {CodePathSegment} segment - The leaved code path segment.
- * @param {ASTNode} node - The current node.
- * @returns {void}
- */
- "onCodePathSegmentEnd": function(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": function(fromSegment, toSegment, node) {
- // do something with segment
- }
- };
-};
-```
-
-### About `onCodePathSegmentLoop`
-
-This event is always fired when the next segment has existed already.
-That timing is the end of loops mainly.
-
-For Example 1:
-
-```js
-while (a) {
- a = foo();
-}
-bar();
-```
-
-1. First, the analysis advances to the end of loop.
-
- ![Loop Event's Example 1](./loop-event-example-while-1.svg)
-
-2. Second, it creates the looping path.
- At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired.
- It fires `onCodePathSegmentLoop` instead.
-
- ![Loop Event's Example 2](./loop-event-example-while-2.svg)
-
-3. Last, it advances to the end.
-
- ![Loop Event's Example 3](./loop-event-example-while-3.svg)
-
-For example 2:
-
-```js
-for (let i = 0; i < 10; ++i) {
- foo(i);
-}
-bar();
-```
-
-1. `for` statements are more complex.
- First, the analysis advances to `ForStatement.update`.
- The `update` segment is hovered at first.
-
- ![Loop Event's Example 1](./loop-event-example-for-1.svg)
-
-2. Second, it advances to `ForStatement.body`.
- Of course the `body` segment is preceded by the `test` segment.
- It keeps the `update` segment hovering.
-
- ![Loop Event's Example 2](./loop-event-example-for-2.svg)
-
-3. Third, it creates the looping path from `body` segment to `update` segment.
- At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired.
- It fires `onCodePathSegmentLoop` instead.
-
- ![Loop Event's Example 3](./loop-event-example-for-3.svg)
-
-4. Fourth, also it creates the looping path from `update` segment to `test` segment.
- At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired.
- It fires `onCodePathSegmentLoop` instead.
-
- ![Loop Event's Example 4](./loop-event-example-for-4.svg)
-
-5. Last, it advances to the end.
-
- ![Loop Event's Example 5](./loop-event-example-for-5.svg)
-
-
-
-## Usage Examples
-
-### To check whether or not this is reachable
-
-```js
-function isReachable(segment) {
- return segment.reachable;
-}
-
-module.exports = function(context) {
- var codePathStack = [];
-
- return {
- // Stores CodePath objects.
- "onCodePathStart": function(codePath) {
- codePathStack.push(codePath);
- },
- "onCodePathEnd": function(codePath) {
- codePathStack.pop();
- },
-
- // Checks reachable or not.
- "ExpressionStatement": function(node) {
- var codePath = codePathStack[codePathStack.length - 1];
-
- // Checks the current code path segments.
- if (!codePath.currentSegments.some(isReachable)) {
- context.report({message: "Unreachable!", node: node});
- }
- }
- };
-};
-```
-
-See Also:
-[no-unreachable](https://github.com/eslint/eslint/blob/master/lib/rules/no-unreachable.js),
-[no-fallthrough](https://github.com/eslint/eslint/blob/master/lib/rules/no-fallthrough.js),
-[consistent-return](https://github.com/eslint/eslint/blob/master/lib/rules/consistent-return.js)
-
-### To check state of a code path
-
-This example is checking whether or not the parameter `cb` is called in every path.
-Instances of `CodePath` and `CodePathSegment` are shared to every rule.
-So a rule must not modify those instances.
-Please use a map of information instead.
-
-```js
-function hasCb(node, context) {
- if (node.type.indexOf("Function") !== -1) {
- return context.getDeclaredVariables(node).some(function(v) {
- return v.type === "Parameter" && v.name === "cb";
- });
- }
- return false;
-}
-
-function isCbCalled(info) {
- return info.cbCalled;
-}
-
-module.exports = function(context) {
- var funcInfoStack = [];
- var segmentInfoMap = Object.create(null);
-
- return {
- // Checks `cb`.
- "onCodePathStart": function(codePath, node) {
- funcInfoStack.push({
- codePath: codePath,
- hasCb: hasCb(node, context)
- });
- },
- "onCodePathEnd": function(codePath, node) {
- funcInfoStack.pop();
-
- // Checks `cb` was called in every paths.
- var cbCalled = codePath.finalSegments.every(function(segment) {
- var 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.
- "onCodePathSegmentStart": function(segment) {
- var funcInfo = funcInfoStack[funcInfoStack - 1];
-
- // Ignores if `cb` doesn't exist.
- if (!funcInfo.hasCb) {
- return;
- }
-
- // Initialize state of this path.
- var 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);
- }
- },
-
- // Checks reachable or not.
- "CallExpression": function(node) {
- var funcInfo = funcInfoStack[funcInfoStack - 1];
-
- // Ignores if `cb` doesn't exist.
- if (!funcInfo.hasCb) {
- return;
- }
-
- // Sets marks that `cb` was called.
- var callee = node.callee;
- if (callee.type === "Identifier" && callee.name === "cb") {
- funcInfo.codePath.currentSegments.forEach(function(segment) {
- var info = segmentInfoMap[segment.id];
- info.cbCalled = true;
- });
- }
- }
- };
-};
-```
-
-See Also:
-[constructor-super](https://github.com/eslint/eslint/blob/master/lib/rules/constructor-super.js),
-[no-this-before-super](https://github.com/eslint/eslint/blob/master/lib/rules/no-this-before-super.js)
-
-## Code Path Examples
-
-### Hello World
-
-```js
-console.log("Hello world!");
-```
-
-![Hello World](./example-hello-world.svg)
-
-### `IfStatement`
-
-```js
-if (a) {
- foo();
-} else {
- bar();
-}
-```
-
-![`IfStatement`](./example-ifstatement.svg)
-
-### `IfStatement` (chain)
-
-```js
-if (a) {
- foo();
-} else if (b) {
- bar();
-} else if (c) {
- hoge();
-}
-```
-
-![`IfStatement` (chain)](./example-ifstatement-chain.svg)
-
-### `SwitchStatement`
-
-```js
-switch (a) {
- case 0:
- foo();
- break;
-
- case 1:
- case 2:
- bar();
- // fallthrough
-
- case 3:
- hoge();
- break;
-}
-```
-
-![`SwitchStatement`](./example-switchstatement.svg)
-
-### `SwitchStatement` (has `default`)
-
-```js
-switch (a) {
- case 0:
- foo();
- break;
-
- case 1:
- case 2:
- bar();
- // fallthrough
-
- case 3:
- hoge();
- break;
-
- default:
- fuga();
- break;
-}
-```
-
-![`SwitchStatement` (has `default`)](./example-switchstatement-has-default.svg)
-
-### `TryStatement` (try-catch)
-
-```js
-try {
- foo();
- if (a) {
- throw new Error();
- }
- bar();
-} catch (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.
-
-![`TryStatement` (try-catch)](./example-trystatement-try-catch.svg)
-
-### `TryStatement` (try-finally)
-
-```js
-try {
- foo();
- bar();
-} finally {
- fuga();
-}
-last();
-```
-
-If there is not `catch` block, `finally` block has two current segments.
-At this time, `CodePath.currentSegments.length` is `2`.
-One is the normal path, and another is the leaving path (`throw` or `return`).
-
-![`TryStatement` (try-finally)](./example-trystatement-try-finally.svg)
-
-### `TryStatement` (try-catch-finally)
-
-```js
-try {
- foo();
- bar();
-} catch (err) {
- hoge(err);
-} finally {
- fuga();
-}
-last();
-```
-
-![`TryStatement` (try-catch-finally)](./example-trystatement-try-catch-finally.svg)
-
-### `WhileStatement`
-
-```js
-while (a) {
- foo();
- if (b) {
- continue;
- }
- bar();
-}
-```
-
-![`WhileStatement`](./example-whilestatement.svg)
-
-### `DoWhileStatement`
-
-```js
-do {
- foo();
- bar();
-} while (a);
-```
-
-![`DoWhileStatement`](./example-dowhilestatement.svg)
-
-### `ForStatement`
-
-```js
-for (let i = 0; i < 10; ++i) {
- foo();
- if (b) {
- break;
- }
- bar();
-}
-```
-
-![`ForStatement`](./example-forstatement.svg)
-
-### `ForStatement` (for ever)
-
-```js
-for (;;) {
- foo();
-}
-bar();
-```
-
-![`ForStatement` (for ever)](./example-forstatement-for-ever.svg)
-
-### `ForInStatement`
-
-```js
-for (let key in obj) {
- foo(key);
-}
-```
-
-![`ForInStatement`](./example-forinstatement.svg)
-
-### When there is a function
-
-```js
-function foo(a) {
- if (a) {
- return;
- }
- bar();
-}
-
-foo(false);
-```
-
-It creates two code paths.
-
-* The global's
-
- ![When there is a function](./example-when-there-is-a-function-g.svg)
-
-* The function's
-
- ![When there is a function](./example-when-there-is-a-function-f.svg)
+[Code Path Analysis Details](../code-path-analysis.md)
## [Signing the CLA](https://openjsf.org/about/the-openjs-foundation-cla/)
-In order to submit code or documentation to an ESLint project, you will need to electronically sign our [Contributor License Agreement](https://cla.js.foundation/eslint/eslint). The CLA is you giving us permission to use your contribution.
+In order to submit code or documentation to an ESLint project, you will need to electronically sign our [Contributor License Agreement](https://github.com/openjs-foundation/easycla). The CLA is the commonly used Apache-style template, and is you giving us permission to use your contribution. You only need to sign the CLA once for any OpenJS Foundation projects that use EasyCLA.
## [Bug Reporting](reporting-bugs)
$ git commit
```
-Our commit message format is as follows:
+All ESLint projects follow [Conventional Commits](https://www.conventionalcommits.org/) for our commit messages. Here's an example commit message:
```
-Tag: Short description (fixes #1234)
+tag: Short description of what you did
Longer description here if necessary
+
+Fixes #1234
```
The first line of the commit message (the summary) must have a specific format. This format is checked by our build tools.
-The `Tag` is one of the following:
+The `tag` is one of the following:
-* `Fix` - for a bug fix.
-* `Update` - either for a backwards-compatible enhancement or for a rule change that adds reported problems.
-* `New` - implemented a new feature.
-* `Breaking` - for a backwards-incompatible enhancement or feature.
-* `Docs` - changes to documentation only.
-* `Build` - changes to build process only.
-* `Upgrade` - for a dependency upgrade.
-* `Chore` - for refactoring, adding tests, etc. (anything that isn't user-facing).
+* `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](working-on-issues.md#issue-labels) to determine the best tag.
-The message summary should be a one-sentence description of the change, and it must be 72 characters in length or shorter. If the pull request addresses an issue, then the issue number should be mentioned at the end. If the commit doesn't completely fix the issue, then use `(refs #1234)` instead of `(fixes #1234)`.
+The message summary should be a one-sentence description of the change, and it must be 72 characters in length or shorter. If the pull request addresses an issue, then the issue number should be mentioned in the body of the commit message in the format `Fixes #1234`. If the commit doesn't completely fix the issue, then use `Refs #1234` instead of `Fixes #1234`.
Here are some good commit message summary examples:
```
-Build: Update Travis to only test Node 0.10 (refs #734)
-Fix: Semi rule incorrectly flagging extra semicolon (fixes #840)
-Upgrade: Esprima to 1.2, switch to using comment attachment (fixes #730)
+build: Update Travis to only test Node 0.10
+fix: Semi rule incorrectly flagging extra semicolon
+chore: Upgrade Esprima to 1.2, switch to using comment attachment
```
The commit message format is important because these messages are used to create a changelog for each release. The tag and issue number help to create more consistent and useful changelogs.
```
git fetch upstream
-git rebase upstream/master
+git rebase upstream/main
```
### Step 4: Run the tests<a name="step4"></a>
$ git push origin issue1234
```
-When updating the code, it's usually better to add additional commits to your branch rather than amending the original commit, because reviewers can easily tell which changes were made in response to a particular review. When we merge pull requests, we will squash all the commits from your branch into a single commit on the `master` branch.
+When updating the code, it's usually better to add additional commits to your branch rather than amending the original commit, because reviewers can easily tell which changes were made in response to a particular review. When we merge pull requests, we will squash all the commits from your branch into a single commit on the `main` branch.
+
+The commit messages in subsequent commits do not need to be in any specific format because these commits do not show up in the changelog.
### Rebasing
```
$ git fetch upstream
-$ git rebase upstream/master
+$ git rebase upstream/main
```
You might find that there are merge conflicts when you attempt to rebase. Please [resolve the conflicts](https://help.github.com/articles/resolving-merge-conflicts-after-a-git-rebase/) and then do a forced push to your branch:
#### npm run lint
-Runs just the JavaScript and JSON linting on the repository
+Runs just the JavaScript and JSON linting on the repository.
#### npm run webpack
-Generates `build/eslint.js`, a version of ESLint for use in the browser
-
-#### npm run docs
-
-Generates JSDoc documentation and places it into `/jsdoc`.
+Generates `build/eslint.js`, a version of ESLint for use in the browser.
* [constructor()][eslint-constructor]
* [lintFiles()][eslint-lintfiles]
* [lintText()][eslint-linttext]
+ * [getRulesMetaForResults()][eslint-getrulesmetaforresults]
* [calculateConfigForFile()][eslint-calculateconfigforfile]
* [isPathIgnored()][eslint-ispathignored]
* [loadFormatter()][eslint-loadformatter]
* [getRules()](#lintergetrules)
* [defineParser()](#linterdefineparser)
* [version](#linterversionlinterversion)
-* [linter (deprecated)](#linter-1)
-* [CLIEngine (deprecated)](#cliengine)
* [RuleTester](#ruletester)
* [Customizing RuleTester](#customizing-ruletester)
-* [Deprecated APIs](#deprecated-apis)
---
* `options.fix` (`boolean | (message: LintMessage) => boolean`)<br>
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` (`("problem" | "suggestion" | "layout")[] | null`)<br>
+* `options.fixTypes` (`("directive" | "problem" | "suggestion" | "layout")[] | null`)<br>
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
* (`Promise<LintResult[]>`)<br>
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)
+
+```js
+const results = await eslint.lintFiles(patterns);
+const rulesMeta = eslint.getRulesMetaForResults(results);
+```
+
+This method returns an object containing meta information for each rule that triggered a lint error in the given `results`.
+
+#### Parameters
+
+* `results` (`LintResult[]`)<br>
+ An array of [LintResult] objects returned from a call to `ESLint#lintFiles()` or `ESLint#lintText()`.
+
+#### Return Value
+
+* (`Object`)<br>
+ 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)
```js
* `fixableWarningCount` (`number`)<br>
The number of warnings that can be fixed automatically by the `fix` constructor option.
* `errorCount` (`number`)<br>
- The number of errors. This includes fixable errors.
+ The number of errors. This includes fixable errors and fatal errors.
+* `fatalErrorCount` (`number`)<br>
+ The number of fatal errors.
* `warningCount` (`number`)<br>
The number of warnings. This includes fixable warnings.
* `output` (`string | undefined`)<br>
`true` if this is a fatal error unrelated to a rule, like a parsing error.
* `message` (`string`)<br>
The error message.
-* `line` (`number`)<br>
+* `line` (`number | undefined`)<br>
The 1-based line number of the begin point of this message.
-* `column` (`number`)<br>
+* `column` (`number | undefined`)<br>
The 1-based column number of the begin point of this message.
* `endLine` (`number | undefined`)<br>
The 1-based line number of the end point of this message. This property is undefined if this message is not a range.
## Linter
-The `Linter` object does the actual evaluation of the JavaScript code. It doesn't do any filesystem operations, it simply parses and reports on the code. In particular, the `Linter` object does not process configuration objects or files.
+The `Linter` object does the actual evaluation of the JavaScript code. It doesn't do any filesystem operations, it simply parses and reports on the code. In particular, the `Linter` object does not process configuration objects or files. Unless you are working in the browser, you probably want to use the [ESLint class](#eslint-class) class instead.
+
The `Linter` is a constructor, and you can create a new instance by passing in the options you want to use. The available options are:
-* `cwd` - Path to a directory that should be considered as the current working directory. It is accessible to rules by calling `context.getCwd()` (see [The Context Object](./working-with-rules.md#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 by calling `context.getCwd()` (see [The Context Object](./working-with-rules.md#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:
The most important method on `Linter` is `verify()`, which initiates linting of the given text. This method accepts three arguments:
* `code` - the source code to lint (a string or instance of `SourceCode`).
-* `config` - a configuration object that has been processed and normalized by CLIEngine using eslintrc files and/or other configuration arguments.
- * **Note**: If you want to lint text and have your configuration be read and processed, use CLIEngine's [`executeOnFiles`](#cliengineexecuteonfiles) or [`executeOnText`](#cliengineexecuteontext) instead.
+* `config` - a configuration object that has been processed and normalized by `ESLint` using eslintrc files and/or other configuration arguments.
+ * **Note**: If you want to lint text and have your configuration be read and processed, use [`ESLint#lintFiles()`][eslint-lintfiles] or [`ESLint#lintText()`][eslint-linttext] instead.
* `options` - (optional) Additional options for this run.
* `filename` - (optional) the filename to associate with the source code.
* `preprocess` - (optional) A function that [Processors in Plugins](/docs/developer-guide/working-with-plugins.md#processors-in-plugins) documentation describes as the `preprocess` method.
Linter.version; // => '4.5.0'
```
-## linter
-
-The `eslint.linter` object (deprecated) is an instance of the `Linter` class as defined [above](#linter). `eslint.linter` exists for backwards compatibility, but we do not recommend using it because any mutations to it are shared among every module that uses `eslint`. Instead, please create your own instance of `eslint.Linter`.
-
-```js
-const linter = require("eslint").linter;
-
-const messages = linter.verify("var foo;", {
- rules: {
- semi: 2
- }
-}, { filename: "foo.js" });
-```
-
-Note: This API is deprecated as of 4.0.0.
-
----
-
-## CLIEngine
-
-⚠️ The `CLIEngine` class has been deprecated in favor of the `ESLint` class as of v7.0.0.
-
-The primary Node.js API is `CLIEngine`, which is the underlying utility that runs the ESLint command line interface. This object will read the filesystem for configuration and file information but will not output any results. Instead, it allows you direct access to the important information so you can deal with the output yourself.
-
-You can get a reference to the `CLIEngine` by doing the following:
-
-```js
-const CLIEngine = require("eslint").CLIEngine;
-```
-
-The `CLIEngine` is a constructor, and you can create a new instance by passing in the options you want to use. The available options are:
-
-* `allowInlineConfig` - Set to `false` to disable the use of configuration comments (such as `/*eslint-disable*/`). Corresponds to `--no-inline-config`.
-* `baseConfig` - Can optionally be set to a config object that has the same schema as `.eslintrc.*`. This will used as a default config, and will be merged with any configuration defined in `.eslintrc.*` files, with the `.eslintrc.*` files having precedence.
-* `cache` - Operate only on changed files (default: `false`). Corresponds to `--cache`.
-* `cacheFile` - Name of the file where the cache will be stored (default: `.eslintcache`). Corresponds to `--cache-file`. Deprecated: use `cacheLocation` instead.
-* `cacheLocation` - Name of the file or directory where the cache will be stored (default: `.eslintcache`). Corresponds to `--cache-location`.
-* `configFile` - The configuration file to use (default: null). If `useEslintrc` is true or not specified, this configuration will be merged with any configuration defined in `.eslintrc.*` files, with options in this configuration having precedence. Corresponds to `-c`.
-* `cwd` - Path to a directory that should be considered as the current working directory.
-* `envs` - An array of environments to load (default: empty array). Corresponds to `--env`. Note: This differs from `.eslintrc.*` / `baseConfig`, where instead the option is called `env` and is an object.
-* `errorOnUnmatchedPattern` - Set to `false` to prevent errors when pattern is unmatched. Corresponds to `--no-error-on-unmatched-pattern`.
-* `extensions` - An array of filename extensions that should be checked for code. The default is an array containing just `".js"`. Corresponds to `--ext`. It is only used in conjunction with directories, not with filenames, glob patterns or when using `executeOnText()`.
-* `fix` - A boolean or a function (default: `false`). If a function, it will be passed each linting message and should return a boolean indicating whether the fix should be included with the output report (errors and warnings will not be listed if fixed). Files on disk are never changed regardless of the value of `fix`. To persist changes to disk, call [`outputFixes()`](#cliengineoutputfixes).
-* `fixTypes` - An array of rule types for which fixes should be applied (default: `null`). This array acts like a filter, only allowing rules of the given types to apply fixes. Possible array values are `"problem"`, `"suggestion"`, and `"layout"`.
-* `globals` - An array of global variables to declare (default: empty array). Corresponds to `--global`, and similarly supports passing `'name:true'` to denote a writeable global. Note: This differs from `.eslintrc.*` / `baseConfig`, where `globals` is an object.
-* `ignore` - False disables use of `.eslintignore`, `ignorePath` and `ignorePattern` (default: true). Corresponds to `--no-ignore`.
-* `ignorePath` - The ignore file to use instead of `.eslintignore` (default: null). Corresponds to `--ignore-path`.
-* `ignorePattern` - Glob patterns for paths to ignore. String or array of strings.
-* `parser` - Specify the parser to be used (default: `espree`). Corresponds to `--parser`.
-* `parserOptions` - An object containing parser options (default: empty object). Corresponds to `--parser-options`.
-* `plugins` - An array of plugins to load (default: empty array). Corresponds to `--plugin`.
-* `reportUnusedDisableDirectives` - When set to `true`, adds reported errors for unused `eslint-disable` directives when no problems would be reported in the disabled area anyway (default: false). Corresponds to `--report-unused-disable-directives`.
-* `resolvePluginsRelativeTo` - Determines the folder where plugins should be resolved from. Should be used when an integration installs plugins and uses those plugins to lint code on behalf of the end user. Corresponds to `--resolve-plugins-relative-to`.
-* `rulePaths` - An array of directories to load custom rules from (default: empty array). Corresponds to `--rulesdir`.
-* `rules` - An object of rules to use (default: null). Corresponds to `--rule`.
-* `useEslintrc` - Set to false to disable use of `.eslintrc` files (default: true). Corresponds to `--no-eslintrc`.
-* `globInputPaths` - Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file.
-
-To programmatically set `.eslintrc.*` options not supported above (such as `extends`,
-`overrides` and `settings`), define them in a config object passed to `baseConfig` instead.
-
-For example:
-
-```js
-const CLIEngine = require("eslint").CLIEngine;
-
-const cli = new CLIEngine({
- baseConfig: {
- extends: ["eslint-config-shared"],
- settings: {
- sharedData: "Hello"
- }
- },
- envs: ["browser", "mocha"],
- useEslintrc: false,
- rules: {
- semi: 2
- }
-});
-```
-
-In this example, a new `CLIEngine` instance is created that extends a configuration called
-`"eslint-config-shared"`, a setting named `"sharedData"` and two environments (`"browser"`
-and `"mocha"`) are defined, loading of `.eslintrc` and `package.json` files are disabled,
-and the `semi` rule enabled as an error. You can then call methods on `cli` and these options
-will be used to perform the correct action.
-
-Note: Currently `CLIEngine` does not validate options passed to it, but may start doing so in the future.
-
-### CLIEngine#executeOnFiles()
-
-If you want to lint one or more files, use the `executeOnFiles()` method. This method accepts a single argument, which is an array of files and/or directories to traverse for files. You can pass the same values as you would using the ESLint command line interface, such as `"."` to search all JavaScript files in the current directory. Here's an example:
-
-```js
-const CLIEngine = require("eslint").CLIEngine;
-
-const cli = new CLIEngine({
- envs: ["browser", "mocha"],
- useEslintrc: false,
- rules: {
- semi: 2
- }
-});
-
-// lint myfile.js and all files in lib/
-const report = cli.executeOnFiles(["myfile.js", "lib/"]);
-```
-
-The return value is an object containing the results of the linting operation. Here's an example of a report object:
-
-```js
-{
- results: [
- {
- filePath: "/Users/eslint/project/myfile.js",
- messages: [{
- ruleId: "semi",
- severity: 2,
- message: "Missing semicolon.",
- line: 1,
- column: 13,
- nodeType: "ExpressionStatement",
- fix: { range: [12, 12], text: ";" }
- }, {
- ruleId: "no-useless-escape",
- severity: 1,
- message: "disallow unnecessary escape characters",
- line: 1,
- column: 10,
- nodeType: "ExpressionStatement",
- suggestions: [{
- desc: "Remove unnecessary escape. This maintains the current functionality.",
- fix: { range: [9, 10], text: "" }
- }, {
- desc: "Escape backslash to include it in the RegExp.",
- fix: { range: [9, 9], text: "\\" }
- }]
- }],
- errorCount: 1,
- warningCount: 1,
- fixableErrorCount: 1,
- fixableWarningCount: 0,
- source: "\"use strict\"\n"
- }
- ],
- errorCount: 1,
- warningCount: 0,
- fixableErrorCount: 1,
- fixableWarningCount: 0,
- usedDeprecatedRules: []
-}
-```
-
-You can also pass `fix: true` when instantiating the `CLIEngine` in order to have it figure out what fixes can be applied.
-
-```js
-const CLIEngine = require("eslint").CLIEngine;
-
-const cli = new CLIEngine({
- envs: ["browser", "mocha"],
- fix: true, // difference from last example
- useEslintrc: false,
- rules: {
- semi: 2,
- quotes: [2, "double"]
- }
-});
-
-// lint myfile.js and all files in lib/
-const report = cli.executeOnFiles(["myfile.js", "lib/"]);
-```
-
-```js
-{
- results: [
- {
- filePath: "/Users/eslint/project/myfile.js",
- messages: [
- {
- ruleId: "semi",
- severity: 2,
- message: "Missing semicolon.",
- line: 1,
- column: 13,
- nodeType: "ExpressionStatement",
- fix: { range: [12, 12], text: ";" }
- },
- {
- ruleId: "func-name-matching",
- severity: 2,
- message: "Function name `bar` should match variable name `foo`",
- line: 2,
- column: 5,
- nodeType: "VariableDeclarator"
- }
- ],
- errorCount: 2,
- warningCount: 0,
- fixableErrorCount: 1,
- fixableWarningCount: 0,
- output: "\"use strict\";\nvar foo = function bar() {};\nfoo();\n"
- }
- ],
- errorCount: 2,
- warningCount: 0,
- fixableErrorCount: 1,
- fixableWarningCount: 0,
- usedDeprecatedRules: []
-}
-```
-
-If the operation ends with a parsing error, you will get a single message for this file, with `fatal: true` added as an extra property.
-
-```js
-{
- results: [
- {
- filePath: "./myfile.js",
- messages: [
- {
- ruleId: null,
- fatal: true,
- severity: 2,
- message: "Parsing error: Unexpected token foo",
- line: 1,
- column: 10
- }
- ],
- errorCount: 1,
- warningCount: 0,
- fixableErrorCount: 0,
- fixableWarningCount: 0,
- source: "function foo() {}"
- }
- ],
- errorCount: 1,
- warningCount: 0,
- fixableErrorCount: 0,
- fixableWarningCount: 0,
- usedDeprecatedRules: []
-}
-```
-
-The top-level report object has a `results` array containing all linting results for files that had warnings or errors (any files that did not produce a warning or error are omitted). Each file result includes:
-
-* `filePath` - Path to the given file.
-* `messages` - Array containing the result of calling `linter.verify()` on the given file.
-* `errorCount` and `warningCount` - The exact number of errors and warnings respectively on the given file.
-* `source` - The source code for the given file. This property is omitted if this file has no errors/warnings or if the `output` property is present.
-* `output` - The source code for the given file with as many fixes applied as possible, so you can use that to rewrite the files if necessary. This property is omitted if no fix is available.
-
-The top-level report object also has `errorCount` and `warningCount` which give the exact number of errors and warnings respectively on all the files. Additionally, `usedDeprecatedRules` signals any deprecated rules used and their replacement (if available). Specifically, it is an array of objects with properties like so:
-
-* `ruleId` - The name of the rule (e.g. `indent-legacy`).
-* `replacedBy` - An array of rules that replace the deprecated rule (e.g. `["indent"]`).
-
-Once you get a report object, it's up to you to determine how to output the results. Fixes will not be automatically applied to the files, even if you set `fix: true` when constructing the `CLIEngine` instance. To apply fixes to the files, call [`outputFixes`](#cliengineoutputfixes).
-
-### CLIEngine#resolveFileGlobPatterns()
-
-You can pass filesystem-style or glob patterns to ESLint and have it function properly. In order to achieve this, ESLint must resolve non-glob patterns into glob patterns before determining which files to execute on. The `resolveFileGlobPatterns()` methods uses the current settings from `CLIEngine` to resolve non-glob patterns into glob patterns. Pass an array of patterns that might be passed to the ESLint CLI and it will return an array of glob patterns that mean the same thing. Here's an example:
-
-```js
-const CLIEngine = require("eslint").CLIEngine;
-
-const cli = new CLIEngine({
-});
-
-// pass an array of patterns
-const globPatterns = cli.resolveFileGlobPatterns(["."]);
-console.log(globPatterns[i]); // ["**/*.js"]
-```
-
-### CLIEngine#getConfigForFile()
-
-If you want to retrieve a configuration object for a given file, use the `getConfigForFile()` method. This method accepts one argument, a file path, and returns an object represented the calculated configuration of the file. Here's an example:
-
-```js
-const CLIEngine = require("eslint").CLIEngine;
-
-const cli = new CLIEngine({
- envs: ["browser", "mocha"],
- useEslintrc: false,
- rules: {
- semi: 2
- }
-});
-
-const config = cli.getConfigForFile("myfile.js");
-```
-
-Once you have the configuration information, you can pass it into the `linter` object:
-
-```js
-const CLIEngine = require("eslint").CLIEngine,
- Linter = require("eslint").Linter;
-
-const linter = new Linter();
-const cli = new CLIEngine({
- envs: ["browser", "mocha"],
- useEslintrc: false,
- rules: {
- semi: 2
- }
-});
-
-const config = cli.getConfigForFile("myfile.js");
-
-const messages = linter.verify('var foo;', config);
-```
-
-### CLIEngine#executeOnText()
-
-If you already have some text to lint, then you can use the `executeOnText()` method to lint that text. The linter will assume that the text is a file in the current working directory, and so will still obey any `.eslintrc` and `.eslintignore` files that may be present. Here's an example:
-
-```js
-const CLIEngine = require("eslint").CLIEngine;
-
-const cli = new CLIEngine({
- envs: ["browser", "mocha"],
- useEslintrc: false,
- rules: {
- semi: 2
- }
-});
-
-// Lint the supplied text and optionally set a filename that is displayed in the report
-const report = cli.executeOnText("var foo = 'bar';", "foo.js");
-
-// In addition to the above, warn if the resolved file name is ignored.
-const reportAndWarnOnIgnoredFile = cli.executeOnText("var foo = 'bar';", "foo.js", true);
-```
-
-The `report` returned from `executeOnText()` is in the same format as from `executeOnFiles()`, but there is only ever one result in `report.results`.
-
-If a filename in the optional second parameter matches a file that is configured to be ignored, then this function returns no errors or warnings. The method includes an additional optional boolean third parameter. When `true`, a resolved file name that is ignored will return a warning.
-
-### CLIEngine#addPlugin()
-
-Loads a plugin from configuration object with specified name. Name can include plugin prefix ("eslint-plugin-")
-
-```js
-const CLIEngine = require("eslint").CLIEngine;
-const cli = new CLIEngine({
- ignore: true
-});
-cli.addPlugin("eslint-plugin-processor", {
- processors: {
- ".txt": {
- preprocess: function(text) {
- return [text];
- },
- postprocess: function(messages) {
- return messages[0];
- }
- }
- }
-});
-```
-
-### CLIEngine#isPathIgnored()
-
-Checks if a given path is ignored by ESLint.
-
-```js
-const CLIEngine = require("eslint").CLIEngine;
-
-const cli = new CLIEngine({
- ignore: true,
- ignorePath: ".customIgnoreFile"
-});
-
-const isIgnored = cli.isPathIgnored("foo/bar.js");
-```
-
-### CLIEngine#getFormatter()
-
-Retrieves a formatter, which you can then use to format a report object. The argument is either the name of a built-in formatter:
-
-* "[checkstyle](../user-guide/formatters#checkstyle)"
-* "[codeframe](../user-guide/formatters#codeframe)"
-* "[compact](../user-guide/formatters#compact)"
-* "[html](../user-guide/formatters#html)"
-* "[jslint-xml](../user-guide/formatters#jslint-xml)"
-* "[json](../user-guide/formatters#json)"
-* "[junit](../user-guide/formatters#junit)"
-* "[stylish](../user-guide/formatters#stylish)" (the default)
-* "[table](../user-guide/formatters#table)"
-* "[tap](../user-guide/formatters#tap)"
-* "[unix](../user-guide/formatters#unix)"
-* "[visualstudio](../user-guide/formatters#visualstudio)"
-
-or the full path to a JavaScript file containing a custom formatter. You can also omit the argument to retrieve the default formatter.
-
-```js
-const CLIEngine = require("eslint").CLIEngine;
-
-const cli = new CLIEngine({
- envs: ["browser", "mocha"],
- useEslintrc: false,
- rules: {
- semi: 2
- }
-});
-
-// lint myfile.js and all files in lib/
-const report = cli.executeOnFiles(["myfile.js", "lib/"]);
-
-// get the default formatter
-const formatter = cli.getFormatter();
-
-// Also could do...
-// const formatter = cli.getFormatter("compact");
-// const formatter = cli.getFormatter("./my/formatter.js");
-
-// output to console
-console.log(formatter(report.results));
-```
-
-**Note:** Also available as a static function on `CLIEngine`.
-
-```js
-// get the default formatter by calling the static function
-const formatter = CLIEngine.getFormatter();
-```
-
-**Important:** You must pass in the `results` property of the report. Passing in `report` directly will result in an error.
-
-### CLIEngine#getErrorResults()
-
-This is a static function on `CLIEngine`. It can be used to filter out all the non error messages from the report object.
-
-```js
-const CLIEngine = require("eslint").CLIEngine;
-
-const cli = new CLIEngine({
- envs: ["browser", "mocha"],
- useEslintrc: false,
- rules: {
- semi: 2
- }
-});
-
-// lint myfile.js and all files in lib/
-const report = cli.executeOnFiles(["myfile.js", "lib/"]);
-
-// only get the error messages
-const errorReport = CLIEngine.getErrorResults(report.results)
-```
-
-**Important:** You must pass in the `results` property of the report. Passing in `report` directly will result in an error.
-
-### CLIEngine#outputFixes()
-
-This is a static function on `CLIEngine` that is used to output fixes from `report` to disk. It does by looking for files that have an `output` property in their results. Here's an example:
-
-```js
-const CLIEngine = require("eslint").CLIEngine;
-
-const cli = new CLIEngine({
- envs: ["browser", "mocha"],
- fix: true,
- useEslintrc: false,
- rules: {
- semi: 2
- }
-});
-
-// lint myfile.js and all files in lib/
-const report = cli.executeOnFiles(["myfile.js", "lib/"]);
-
-// output fixes to disk
-CLIEngine.outputFixes(report);
-```
-
-### CLIEngine#getRules()
-
-This method returns a map of all loaded rules. Under the hood, it calls [Linter#getRules](#lintergetrules).
-
-```js
-const CLIEngine = require("eslint").CLIEngine;
-const cli = new CLIEngine();
-
-cli.getRules();
-
-/*
-Map {
- 'accessor-pairs' => { meta: { docs: [Object], schema: [Array] }, create: [Function: create] },
- 'array-bracket-newline' => { meta: { docs: [Object], schema: [Array] }, create: [Function: create] },
- ...
-}
-*/
-```
-
-
-### CLIEngine.version
-
-`CLIEngine` has a static `version` property containing the semantic version number of ESLint that it comes from.
-
-```js
-require("eslint").CLIEngine.version; // '4.5.0'
-```
-
---
## RuleTester
A test case is an object with the following properties:
+* `name` (string, optional): The name to use for the test case, to make it easier to find
* `code` (string, required): The source code that the rule should be run on
* `options` (array, optional): The options passed to the rule. The rule severity should not be included in this list.
* `filename` (string, optional): The filename for the given case (useful for rules that make assertions about filenames).
+* `only` (boolean, optional): Run this case exclusively for debugging in supported test frameworks.
In addition to the properties above, invalid test cases can also have the following properties:
`RuleTester` depends on two functions to run tests: `describe` and `it`. These functions can come from various places:
1. If `RuleTester.describe` and `RuleTester.it` have been set to function values, `RuleTester` will use `RuleTester.describe` and `RuleTester.it` to run tests. You can use this to customize the behavior of `RuleTester` to match a test framework that you're using.
-1. Otherwise, if `describe` and `it` are present as globals, `RuleTester` will use `global.describe` and `global.it` to run tests. This allows `RuleTester` to work when using frameworks like [Mocha](https://mochajs.org/) without any additional configuration.
-1. Otherwise, `RuleTester#run` will simply execute all of the tests in sequence, and will throw an error if one of them fails. This means you can simply execute a test file that calls `RuleTester.run` using `node`, without needing a testing framework.
-`RuleTester#run` calls the `describe` function with two arguments: a string describing the rule, and a callback function. The callback calls the `it` function with a string describing the test case, and a test function. The test function will return successfully if the test passes, and throw an error if the test fails. (Note that this is the standard behavior for test suites when using frameworks like [Mocha](https://mochajs.org/); this information is only relevant if you plan to customize `RuleTester.it` and `RuleTester.describe`.)
+ If `RuleTester.itOnly` has been set to a function value, `RuleTester` will call `RuleTester.itOnly` instead of `RuleTester.it` to run cases with `only: true`. If `RuleTester.itOnly` is not set but `RuleTester.it` has an `only` function property, `RuleTester` will fall back to `RuleTester.it.only`.
+
+2. Otherwise, if `describe` and `it` are present as globals, `RuleTester` will use `global.describe` and `global.it` to run tests and `global.it.only` to run cases with `only: true`. This allows `RuleTester` to work when using frameworks like [Mocha](https://mochajs.org/) without any additional configuration.
+3. Otherwise, `RuleTester#run` will simply execute all of the tests in sequence, and will throw an error if one of them fails. This means you can simply execute a test file that calls `RuleTester.run` using `Node.js`, without needing a testing framework.
+
+`RuleTester#run` calls the `describe` function with two arguments: a string describing the rule, and a callback function. The callback calls the `it` function with a string describing the test case, and a test function. The test function will return successfully if the test passes, and throw an error if the test fails. The signature for `only` is the same as `it`. `RuleTester` calls either `it` or `only` for every case even when some cases have `only: true`, and the test framework is responsible for implementing test case exclusivity. (Note that this is the standard behavior for test suites when using frameworks like [Mocha](https://mochajs.org/); this information is only relevant if you plan to customize `RuleTester.describe`, `RuleTester.it`, or `RuleTester.itOnly`.)
Example of customizing `RuleTester`:
---
-## Deprecated APIs
-
-* `cli` - the `cli` object has been deprecated in favor of `CLIEngine`. As of v1.0.0, `cli` is no longer exported and should not be used by external tools.
-* `linter` - the `linter` object has been deprecated in favor of `Linter` as of v4.0.0.
-* `CLIEngine` - the `CLIEngine` class has been deprecated in favor of the `ESLint` class as of v7.0.0.
-
----
-
[configuration object]: ../user-guide/configuring
[builtin-formatters]: https://eslint.org/docs/user-guide/formatters/
[thirdparty-formatters]: https://www.npmjs.com/search?q=eslintformatter
[eslint-constructor]: #-new-eslintoptions
[eslint-lintfiles]: #-eslintlintfilespatterns
[eslint-linttext]: #-eslintlinttextcode-options
+[eslint-getrulesmetaforresults]: #-eslintgetrulesmetaforresultsresults
[eslint-calculateconfigforfile]: #-eslintcalculateconfigforfilefilepath
[eslint-ispathignored]: #-eslintispathignoredfilepath
[eslint-loadformatter]: #-eslintloadformatternameorpath
#### type
* **Type:** `string`
-* **Description:** The type of this scope. This is one of `"block"`, `"catch"`, `"class"`, `"for"`, `"function"`, `"function-expression-name"`, `"global"`, `"module"`, `"switch"`, `"with"`
+* **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
#### variableScope
* **Type:** `Scope`
-* **Description:** The scope which hosts variables which are defined by `var` declarations.
+* **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
* wildcard (matches all nodes): `*`
* attribute existence: `[attr]`
* attribute value: `[attr="foo"]` or `[attr=123]`
-* attribute regex: `[attr=/foo.*/]`
+* attribute regex: `[attr=/foo.*/]` <sub>(with some [known issues](#known-issues))</sub>
* attribute conditions: `[attr!="foo"]`, `[attr>2]`, `[attr<3]`, `[attr>=2]`, or `[attr<=3]`
* nested attribute: `[attr.level2="foo"]`
* field: `FunctionDeclaration > Identifier.id`
```
Using selectors in the `no-restricted-syntax` rule can give you a lot of control over problematic patterns in your codebase, without needing to write custom rules to detect each pattern.
+
+### Known issues
+
+Due to a [bug](https://github.com/estools/esquery/issues/68) in [esquery](https://github.com/estools/esquery), regular expressions that contain a forward-slash character `/` aren't properly parsed, so `[value=/some\/path/]` will be a syntax error. As a [workaround](https://github.com/estools/esquery/issues/68), you can replace the `/` character with its unicode counterpart, like so: `[value=/some\\u002Fpath/]`.
## Running Individual Tests
-If you want to quickly run just one test, you can do so by running Mocha directly and passing in the filename. For example:
+If you want to quickly run just one test file, you can do so by running Mocha directly and passing in the filename. For example:
npm run test:cli tests/lib/rules/no-wrap-func.js
-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.
+If you want to run just one or a subset of `RuleTester` test cases, add `only: true` to each test case or wrap the test case in `RuleTester.only(...)` to add it automatically:
+
+```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
+ ]
+})
+```
+
+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.
## More Control on Unit Testing
ESLint provides the [`RuleTester`](/docs/developer-guide/nodejs-api.md#ruletester) utility to make it easy to test the rules of your plugin.
+### Linting
+
+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-node](https://www.npmjs.com/package/eslint-plugin-node)
+
## Share Plugins
In order to make your plugin available to the community you have to publish it on npm.
The `npm run perf` command gives a high-level overview of ESLint running time with default rules (`eslint:recommended`) enabled.
```bash
-$ git checkout master
-Switched to branch 'master'
+$ git checkout main
+Switched to branch 'main'
$ npm run perf
CPU Speed is 2200 with multiplier 7500000
// Rule Definition
//------------------------------------------------------------------------------
+/**
+ * @type {import('eslint').Rule.RuleModule}
+ */
module.exports = {
meta: {
type: "suggestion",
* `description` (string) provides the short description of the rule in the [rules index](../rules/)
* `category` (string) specifies the heading under which the rule is listed in the [rules index](../rules/)
* `recommended` (boolean) is whether the `"extends": "eslint:recommended"` property in a [configuration file](../user-guide/configuring/configuration-files.md#extending-configuration-files) enables the rule
- * `url` (string) specifies the URL at which the full documentation can be accessed
- * `suggestion` (boolean) specifies whether rules can return suggestions (defaults to false if omitted)
+ * `url` (string) specifies the URL at which the full documentation can be accessed (enabling code editors to provide a helpful link on highlighted rule violations)
In a custom rule or plugin, you can omit `docs` or include any properties that you need in it.
-* `fixable` (string) is either `"code"` or `"whitespace"` if the `--fix` option on the [command line](../user-guide/command-line-interface.md#fix) automatically fixes problems reported by the rule
+* `fixable` (string) is either `"code"` or `"whitespace"` if the `--fix` option on the [command line](../user-guide/command-line-interface.md#--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.
+* `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.
+
* `schema` (array) specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../user-guide/configuring/rules.md#configuring-rules)
* `deprecated` (boolean) indicates whether the rule has been deprecated. You may omit the `deprecated` property if the rule has not been deprecated.
Additionally, the `context` object has the following methods:
* `getAncestors()` - returns an array of the ancestors of the currently-traversed node, starting at the root of the AST and continuing through the direct parent of the current node. This array does not include the currently-traversed node itself.
-* `getCwd()` - returns the `cwd` passed to [Linter](./nodejs-api.md#Linter). It is a path to a directory that should be considered as the current working directory.
+* `getCwd()` - returns the `cwd` passed to [Linter](./nodejs-api.md#linter). It is a path to a directory that should be considered as the current working directory.
* `getDeclaredVariables(node)` - returns a list of [variables](./scope-manager-interface.md#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.
* `replaceText(nodeOrToken, text)` - replaces the text in the given node or token
* `replaceTextRange(range, text)` - replaces the text in the given range
+A range is a two-item array containing character indices inside of 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:
* 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`](/docs/rules/quotes.md) 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.
+
+The following example replaces `node` and also ensures that no other fixes will be applied in the range of `node.parent` in the same pass:
+
+```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, "");
+ }
+});
+```
+
### Providing Suggestions
In some cases fixes aren't appropriate to be automatically applied, for example, if a fix potentially changes functionality or if there are multiple valid ways to fix a rule depending on the implementation intent (see the best practices for [applying fixes](#applying-fixes) listed above). In these cases, there is an alternative `suggest` option on `context.report()` that allows other tools, such as editors, to expose helpers for users to manually apply a suggestion.
{% endraw %}
```
+**Important:** The `meta.hasSuggestions` property is mandatory for rules that provide suggestions. ESLint will throw an error if a rule attempts to produce a suggestion but does not [export](#rule-basics) this property.
+
Note: Suggestions will be applied as a stand-alone change, without triggering multipass fixes. Each suggestion should focus on a singular change in the code and should not try to conform to user defined styles. For example, if a suggestion is adding a new statement into the codebase, it should not try to match correct indentation, or conform to user preferences on presence/absence of semicolons. All of those things can be corrected by multipass autofix when the user triggers it.
Best practices for suggestions:
#### Suggestion `messageId`s
-Instead of using a `desc` key for suggestions a `messageId` can be used instead. This works the same way as `messageId`s for the overall error (see [messageIds](#messageIds)). Here is an example of how to use it in a rule:
+Instead of using a `desc` key for suggestions a `messageId` can be used instead. This works the same way as `messageId`s for the overall error (see [messageIds](#messageids)). Here is an example of how to use it in a rule:
```js
{% raw %}
unnecessaryEscape: "Unnecessary escape character: \\{{character}}.",
removeEscape: "Remove the `\\`. This maintains the current functionality.",
escapeBackslash: "Replace the `\\` with `\\\\` to include the actual backslash character."
- }
+ },
+ hasSuggestions: true
},
create: function(context) {
// ...
messages: {
unnecessaryEscape: "Unnecessary escape character: \\{{character}}.",
removeEscape: "Remove `\\` before {{character}}.",
- }
+ },
+ hasSuggestions: true
},
create: function(context) {
// ...
};
```
-Once you have an instance of `SourceCode`, you can use the methods on it to work with the code:
+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.
* `getAllComments()` - returns an array of all comments in the source.
When developing in the ESLint core repository, the `npm run perf` command gives a high-level overview of ESLint running time with all core rules enabled.
```bash
-$ git checkout master
-Switched to branch 'master'
+$ git checkout main
+Switched to branch 'main'
$ npm run perf
CPU Speed is 2200 with multiplier 7500000
}
console.log(y);
}
+
+class C {
+ static {
+ if (something) {
+ var build = true;
+ }
+ build = false;
+ }
+}
```
Examples of **correct** code for this rule:
console.log(y);
}
}
+
+class C {
+ static {
+ var build = false;
+ if (something) {
+ build = true;
+ }
+ }
+}
```
## Further Reading
function baz() {let i = 0;
return i;
}
+
+class C {
+ static {this.bar = 0;}
+}
```
Examples of **correct** code for this rule with the default `"always"` option:
function foo() { return true; }
if (foo) { bar = 0; }
+
+class C {
+ static { this.bar = 0; }
+}
```
### never
function foo() { return true; }
if (foo) { bar = 0;}
+
+class C {
+ static { this.bar = 0; }
+}
```
Examples of **correct** code for this rule with the `"never"` option:
function foo() {return true;}
if (foo) {bar = 0;}
+
+class C {
+ static {this.bar = 0;}
+}
```
## When Not To Use It
else {
baz();
}
+
+class C
+{
+ static
+ {
+ foo();
+ }
+}
```
Examples of **correct** code for this rule with the default `"1tbs"` option:
handleError();
}
+class C {
+ static {
+ foo();
+ }
+}
+
// when there are no braces, there are no problems
if (foo) bar();
else if (baz) boom();
try { somethingRisky(); } catch(e) {
handleError();
}
+
+class C {
+ static { foo(); }
+}
+
+class D { static { foo(); } }
```
### stroustrup
handleError();
}
+class C
+{
+ static
+ {
+ foo();
+ }
+}
+
if (foo) {
bar();
} else {
handleError();
}
+class C {
+ static {
+ foo();
+ }
+}
+
// when there are no braces, there are no problems
if (foo) bar();
else if (baz) boom();
try { somethingRisky(); }
catch(e) { handleError(); }
+
+class C {
+ static { foo(); }
+}
+
+class D { static { foo(); } }
```
### allman
handleError();
}
+class C {
+ static {
+ foo();
+ }
+}
+
if (foo) {
bar();
} else {
handleError();
}
+class C
+{
+ static
+ {
+ foo();
+ }
+}
+
// when there are no braces, there are no problems
if (foo) bar();
else if (baz) boom();
try { somethingRisky(); }
catch(e) { handleError(); }
+
+class C
+{
+ static { foo(); }
+
+ static
+ { foo(); }
+}
+
+class D { static { foo(); } }
```
## When Not To Use It
static foo() {
// OK. static methods aren't expected to use this.
}
+
+ static {
+ // OK. static blocks are exempt.
+ }
}
```
## Options
-### Exceptions
+This rule has two options:
+
+* `"exceptMethods"` allows specified method names to be ignored with this rule.
+* `"enforceForClassFields"` enforces that functions used as instance field initializers utilize `this`. (default: `true`)
+
+### exceptMethods
```
"class-methods-use-this": [<enabled>, { "exceptMethods": [<...exceptions>] }]
Examples of **correct** code for this rule when used with exceptMethods:
```js
-/*eslint class-methods-use-this: ["error", { "exceptMethods": ["foo"] }] */
+/*eslint class-methods-use-this: ["error", { "exceptMethods": ["foo", "#bar"] }] */
class A {
foo() {
}
+ #bar() {
+ }
+}
+```
+
+## enforceForClassFields
+
+```
+"class-methods-use-this": [<enabled>, { "enforceForClassFields": true | false }]
+```
+
+The `enforceForClassFields` option enforces that arrow functions and function expressions used as instance field initializers utilize `this`. (default: `true`)
+
+Examples of **incorrect** code for this rule with the `{ "enforceForClassFields": true }` option (default):
+
+```js
+/*eslint class-methods-use-this: ["error", { "enforceForClassFields": true }] */
+
+class A {
+ foo = () => {}
+}
+```
+
+Examples of **correct** code for this rule with the `{ "enforceForClassFields": true }` option (default):
+
+```js
+/*eslint class-methods-use-this: ["error", { "enforceForClassFields": true }] */
+
+class A {
+ foo = () => {this;}
+}
+```
+
+Examples of **correct** code for this rule with the `{ "enforceForClassFields": false }` option:
+
+```js
+/*eslint class-methods-use-this: ["error", { "enforceForClassFields": false }] */
+
+class A {
+ foo = () => {}
}
```
}
```
+Class field initializers and class static blocks are implicit functions. Therefore, their complexity is calculated separately for each initializer and each static block, and it doesn't contribute to the complexity of the enclosing code.
+
+Examples of additional **incorrect** code for a maximum of 2:
+
+```js
+/*eslint complexity: ["error", 2]*/
+
+class C {
+ x = a || b || c; // this initializer has complexity = 3
+}
+
+class D { // this static block has complexity = 3
+ static {
+ if (foo) {
+ bar = baz || qux;
+ }
+ }
+}
+```
+
+Examples of additional **correct** code for a maximum of 2:
+
+```js
+/*eslint complexity: ["error", 2]*/
+
+function foo() { // this function has complexity = 1
+ class C {
+ x = a + b; // this initializer has complexity = 1
+ y = c || d; // this initializer has complexity = 2
+ z = e && f; // this initializer has complexity = 2
+
+ static p = g || h; // this initializer has complexity = 2
+ static q = i ? j : k; // this initializer has complexity = 2
+
+ static { // this static block has complexity = 2
+ if (foo) {
+ baz = bar;
+ }
+ }
+
+ static { // this static block has complexity = 2
+ qux = baz || quux;
+ }
+ }
+}
+```
+
## Options
Optionally, you may specify a `max` object property:
var x = foo["class"]; // Property name is a reserved word, square-bracket notation required
```
+Examples of additional **correct** code for the `{ "allowKeywords": false }` option:
+
+```js
+/*eslint dot-notation: ["error", { "allowKeywords": false }]*/
+
+class C {
+ #in;
+ foo() {
+ this.#in; // Dot notation is required for private identifiers
+ }
+}
+```
+
### allowPattern
For example, when preparing data to be sent to an external API, it is often required to use property names that include underscores. If the `camelcase` rule is in effect, these [snake case](https://en.wikipedia.org/wiki/Snake_case) properties would not be allowed. By providing an `allowPattern` to the `dot-notation` rule, these snake case properties can be accessed with bracket notation.
obj['foo'] = function bar() {};
var obj = {foo: function bar() {}};
({['foo']: function bar() {}});
+
+class C {
+ foo = function bar() {};
+}
```
```js
obj['foo'] = function foo() {};
var obj = {foo: function foo() {}};
({['foo']: function foo() {}});
+
+class C {
+ foo = function foo() {};
+}
```
Examples of **correct** code for this rule:
var [ bar ] = [ function bar(){} ];
({[foo]: function bar() {}})
+class C {
+ foo = function foo() {};
+ baz = function() {};
+}
+
+// private names are ignored
+class D {
+ #foo = function foo() {};
+ #bar = function foo() {};
+ baz() {
+ this.#foo = function foo() {};
+ this.#foo = function bar() {};
+ }
+}
+
module.exports = function foo(name) {};
module['exports'] = function foo(name) {};
```
var [ bar ] = [ function bar(){} ];
({[foo]: function bar() {}})
+class C {
+ foo = function bar() {};
+ baz = function() {};
+}
+
+// private names are ignored
+class D {
+ #foo = function foo() {};
+ #bar = function foo() {};
+ baz() {
+ this.#foo = function foo() {};
+ this.#foo = function bar() {};
+ }
+}
+
module.exports = function foo(name) {};
module['exports'] = function foo(name) {};
```
This rule has a string option:
* `"always"` (default) requires function expressions to have a name
-* `"as-needed"` requires function expressions to have a name, if the name cannot be assigned automatically in an ES6 environment
+* `"as-needed"` requires function expressions to have a name, if the name isn't assigned automatically per the ECMAScript specification.
* `"never"` disallows named function expressions, except in recursive functions, where a name is needed
This rule has an object option:
* `"generators": "always" | "as-needed" | "never"`
* `"always"` require named generators
- * `"as-needed"` require named generators if the name cannot be assigned automatically in an ES6 environment.
+ * `"as-needed"` require named generators if the name isn't assigned automatically per the ECMAScript specification.
* `"never"` disallow named generators where possible.
When a value for `generators` is not provided the behavior for generator functions falls back to the base option.
meow: function() {}
}
+class C {
+ #bar = function() {};
+ baz = function() {};
+}
+
+quux ??= function() {};
+
(function bar() {
// ...
}())
- variable declarations
- function declarations
- object properties assigned to during object creation
+- class fields
+- class methods
It will not catch disallowed identifiers that are:
var itemSet = {
data: [...]
};
+
+class Foo {
+ data = [];
+}
+
+class Foo {
+ #data = [];
+}
+
+class Foo {
+ callback( {);
+}
+
+class Foo {
+ #callback( {);
+}
```
Examples of **correct** code for this rule with sample `"data", "callback"` restricted identifiers:
foo.callback(); // all function calls are ignored
foo.data; // all property names that are not assignments are ignored
+
+class Foo {
+ items = [];
+}
+
+class Foo {
+ #items = [];
+}
+
+class Foo {
+ method( {);
+}
+
+class Foo {
+ #method( {);
+}
```
## When Not To Use It
(a) => { a * a };
class x { }
class Foo { x() {} }
+class Foo { #x() {} }
+class Foo { x = 1 }
+class Foo { #x = 1 }
function foo(...x) { }
function foo([x]) { }
var [x] = arr;
function foo(num = 0) { }
class MyClass { }
class Foo { method() {} }
+class Foo { #method() {} }
+class Foo { field = 1 }
+class Foo { #field = 1 }
function foo(...args) { }
function foo([longName]) { }
var { prop } = {};
function do_something() {
// ...
}
+
obj.do_something = function() {
// ...
};
+
+class My_Class {}
+
+class myClass {
+ do_something() {}
+}
+
+class myClass {
+ #do_something() {}
+}
```
Examples of **correct** code for this rule with the `"^[a-z]+([A-Z][a-z]+)*$"` option:
var obj = {
my_pref: 1
};
+
+class myClass {}
+
+class myClass {
+ doSomething() {}
+}
+
+class myClass {
+ #doSomething() {}
+}
```
This rule has an object option:
-* `"properties": true` requires object properties to match the specified regular expression
+* `"properties": false` (default) does not check object properties
+* `"properties": true` requires object literal properties and member expression assignment properties to match the specified regular expression
+* `"classFields": false` (default) does not class field names
+* `"classFields": true` requires class field names to match the specified regular expression
+* `"onlyDeclarations": false` (default) requires all variable names to match the specified regular expression
* `"onlyDeclarations": true` requires only `var`, `function`, and `class` declarations to match the specified regular expression
-* `"onlyDeclarations": false` requires all variable names to match the specified regular expression
* `"ignoreDestructuring": false` (default) enforces `id-match` for destructured identifiers
* `"ignoreDestructuring": true` does not check destructured identifiers
};
```
+### classFields
+
+Examples of **incorrect** code for this rule with the `"^[a-z]+([A-Z][a-z]+)*$", { "classFields": true }` options:
+
+```js
+/*eslint id-match: ["error", "^[a-z]+([A-Z][a-z]+)*$", { "properties": true }]*/
+
+class myClass {
+ my_pref = 1;
+}
+
+class myClass {
+ #my_pref = 1;
+}
+```
+
### onlyDeclarations
Examples of **correct** code for this rule with the `"^[a-z]+([A-Z][a-z]+)*$", { "onlyDeclarations": true }` options:
# enforce consistent indentation (indent-legacy)
+This rule was **deprecated** in ESLint v4.0.0.
+
ESLint 4.0.0 introduced a rewrite of the [`indent`](/docs/rules/indent) rule, which now reports more errors than it did in previous versions. To ease the process of migrating to 4.0.0, the `indent-legacy` rule was introduced as a snapshot of the `indent` rule from ESLint 3.x. If your build is failing after the upgrade to 4.0.0, you can disable `indent` and enable `indent-legacy` as a quick fix. Eventually, you should switch back to the `indent` rule to get bugfixes and improvements in future versions.
---
* `"FunctionExpression"` takes an object to define rules for function expressions.
* `parameters` (default: 1) enforces indentation level for parameters in a function expression. This can either be a number indicating indentation level, or the string `"first"` indicating that all parameters of the expression must be aligned with the first parameter. This can also be set to `"off"` to disable checking for FunctionExpression parameters.
* `body` (default: 1) enforces indentation level for the body of a function expression.
+* `"StaticBlock"` takes an object to define rules for class static blocks.
+ * `body` (default: 1) enforces indentation level for the body of a class static block.
* `"CallExpression"` takes an object to define rules for function call expressions.
* `arguments` (default: 1) enforces indentation level for arguments in a call expression. This can either be a number indicating indentation level, or the string `"first"` indicating that all arguments of the expression must be aligned with the first argument. This can also be set to `"off"` to disable checking for CallExpression arguments.
* `"ArrayExpression"` (default: 1) enforces indentation level for elements in arrays. It can also be set to the string `"first"`, indicating that all the elements in the array should be aligned with the first element. This can also be set to `"off"` to disable checking for array elements.
}
```
+### StaticBlock
+
+Examples of **incorrect** code for this rule with the `2, { "StaticBlock": {"body": 1} }` option:
+
+```js
+/*eslint indent: ["error", 2, { "StaticBlock": {"body": 1} }]*/
+
+class C {
+ static {
+ foo();
+ }
+}
+```
+
+Examples of **correct** code for this rule with the `2, { "StaticBlock": {"body": 1} }` option:
+
+```js
+/*eslint indent: ["error", 2, { "StaticBlock": {"body": 1} }]*/
+
+class C {
+ static {
+ foo();
+ }
+}
+```
+
+Examples of **incorrect** code for this rule with the `2, { "StaticBlock": {"body": 2} }` option:
+
+```js
+/*eslint indent: ["error", 2, { "StaticBlock": {"body": 2} }]*/
+
+class C {
+ static {
+ foo();
+ }
+}
+```
+
+Examples of **correct** code for this rule with the `2, { "StaticBlock": {"body": 2} }` option:
+
+```js
+/*eslint indent: ["error", 2, { "StaticBlock": {"body": 2} }]*/
+
+class C {
+ static {
+ foo();
+ }
+}
+```
+
### CallExpression
Examples of **incorrect** code for this rule with the `2, { "CallExpression": {"arguments": 1} }` option:
## Rule Details
-This rule enforces consistent spacing around keywords and keyword-like tokens: `as` (in module declarations), `async` (of async functions), `await` (of await expressions), `break`, `case`, `catch`, `class`, `const`, `continue`, `debugger`, `default`, `delete`, `do`, `else`, `export`, `extends`, `finally`, `for`, `from` (in module declarations), `function`, `get` (of getters), `if`, `import`, `in`, `instanceof`, `let`, `new`, `of` (in for-of statements), `return`, `set` (of setters), `static`, `super`, `switch`, `this`, `throw`, `try`, `typeof`, `var`, `void`, `while`, `with`, and `yield`. This rule is designed carefully not to conflict with other spacing rules: it does not apply to spacing where other rules report problems.
+This rule enforces consistent spacing around keywords and keyword-like tokens: `as` (in module declarations), `async` (of async functions), `await` (of await expressions), `break`, `case`, `catch`, `class`, `const`, `continue`, `debugger`, `default`, `delete`, `do`, `else`, `export`, `extends`, `finally`, `for`, `from` (in module declarations), `function`, `get` (of getters), `if`, `import`, `in` (in for-in statements), `let`, `new`, `of` (in for-of statements), `return`, `set` (of setters), `static`, `super`, `switch`, `this`, `throw`, `try`, `typeof`, `var`, `void`, `while`, `with`, and `yield`. This rule is designed carefully not to conflict with other spacing rules: it does not apply to spacing where other rules report problems.
## Options
### overrides
-Examples of **correct** code for this rule with the `{ "overrides": { "if": { "after": false }, "for": { "after": false }, "while": { "after": false } } }` option:
+Examples of **correct** code for this rule with the `{ "overrides": { "if": { "after": false }, "for": { "after": false }, "while": { "after": false }, "static": { "after": false } } }` option:
```js
/*eslint keyword-spacing: ["error", { "overrides": {
"if": { "after": false },
"for": { "after": false },
- "while": { "after": false }
+ "while": { "after": false },
+ "static": { "after": false }
} }]*/
if(foo) {
for(;;);
while(true) {
- //...
+ //...
+}
+
+class C {
+ static{
+ //...
+ }
}
```
* `"afterBlockComment": true` requires an empty line after block comments
* `"beforeLineComment": true` requires an empty line before line comments
* `"afterLineComment": true` requires an empty line after line comments
-* `"allowBlockStart": true` allows comments to appear at the start of block statements
-* `"allowBlockEnd": true` allows comments to appear at the end of block statements
+* `"allowBlockStart": true` allows comments to appear at the start of block statements, function bodies, classes, and class static blocks
+* `"allowBlockEnd": true` allows comments to appear at the end of block statements, function bodies, classes, and class static blocks
* `"allowObjectStart": true` allows comments to appear at the start of object literals
* `"allowObjectEnd": true` allows comments to appear at the end of object literals
* `"allowArrayStart": true` allows comments to appear at the start of array literals
var day = "great"
return day;
}
+
+if (bar) {
+ // what a great and wonderful day
+ foo();
+}
+
+class C {
+ // what a great and wonderful day
+
+ method() {
+ // what a great and wonderful day
+ foo();
+ }
+
+ static {
+ // what a great and wonderful day
+ foo();
+ }
+}
```
Examples of **correct** code for this rule with the `{ "beforeBlockComment": true, "allowBlockStart": true }` options:
var day = "great"
return day;
}
+
+if (bar) {
+ /* what a great and wonderful day */
+ foo();
+}
+
+class C {
+ /* what a great and wonderful day */
+
+ method() {
+ /* what a great and wonderful day */
+ foo();
+ }
+
+ static {
+ /* what a great and wonderful day */
+ foo();
+ }
+}
```
### allowBlockEnd
return day;
// what a great and wonderful day
}
+
+if (bar) {
+ foo();
+ // what a great and wonderful day
+}
+
+class C {
+
+ method() {
+ foo();
+ // what a great and wonderful day
+ }
+
+ static {
+ foo();
+ // what a great and wonderful day
+ }
+
+ // what a great and wonderful day
+}
```
Examples of **correct** code for this rule with the `{ "afterBlockComment": true, "allowBlockEnd": true }` option:
/* what a great and wonderful day */
}
+
+if (bar) {
+ foo();
+
+ /* what a great and wonderful day */
+}
+
+class C {
+
+ method() {
+ foo();
+
+ /* what a great and wonderful day */
+ }
+
+ static {
+ foo();
+
+ /* what a great and wonderful day */
+ }
+
+ /* what a great and wonderful day */
+}
```
### allowClassStart
```js
/* eslint lines-between-class-members: ["error", "always"]*/
class MyClass {
+ x;
foo() {
//...
}
```js
/* eslint lines-between-class-members: ["error", "always"]*/
class MyClass {
+ x;
+
foo() {
//...
}
}
```
+Examples of additional **correct** code for this rule:
+
+```js
+/* eslint lines-between-class-members: ["error", "always"]*/
+class MyClass {
+ x = 1
+
+ ;in = 2
+}
+```
+
### Options
This rule has a string option and an object option.
```js
/* eslint lines-between-class-members: ["error", "always"]*/
class Foo{
+ x;
bar(){}
baz(){}
}
/* eslint lines-between-class-members: ["error", "never"]*/
class Foo{
+ x;
+
bar(){}
baz(){}
```js
/* eslint lines-between-class-members: ["error", "always"]*/
class Foo{
+ x;
+
bar(){}
baz(){}
/* eslint lines-between-class-members: ["error", "never"]*/
class Foo{
+ x;
bar(){}
baz(){}
}
```js
/* eslint lines-between-class-members: ["error", "always", { "exceptAfterSingleLine": true }]*/
class Foo{
+ x; // single line class member
bar(){} // single line class member
baz(){
// multi line class member
## Options
-This rule has a numeric option (defaulted to 1) to specify the
-maximum number of classes.
+This rule may be configured with either an object or a number.
+
+If the option is an object, it may contain one or both of:
+
+- `ignoreExpressions`: a boolean option (defaulted to `false`) to ignore class expressions.
+- `max`: a numeric option (defaulted to 1) to specify the maximum number of classes.
For example:
}
```
-Examples of **correct** code for this rule with the numeric option set to `2`:
+```json
+{
+ "max-classes-per-file": [
+ "error",
+ { "ignoreExpressions": true, "max": 2 }
+ ]
+}
+```
+
+Examples of **correct** code for this rule with the `max` option set to `2`:
```js
/* eslint max-classes-per-file: ["error", 2] */
class Foo {}
class Bar {}
```
+
+Examples of **correct** code for this rule with the `ignoreExpressions` option set to `true`:
+
+```js
+/* eslint max-classes-per-file: ["error", { ignoreExpressions: true }] */
+
+class VisitorFactory {
+ forDescriptor(descriptor) {
+ return class {
+ visit(node) {
+ return `Visiting ${descriptor}.`;
+ }
+ };
+ }
+}
+```
```js
/*eslint max-depth: ["error", 4]*/
-/*eslint-env es6*/
function foo() {
for (;;) { // Nested 1 deep
```js
/*eslint max-depth: ["error", 4]*/
-/*eslint-env es6*/
function foo() {
for (;;) { // Nested 1 deep
}
```
+Note that class static blocks do not count as nested blocks, and that the depth in them is calculated separately from the enclosing context.
+
+Examples of **incorrect** code for this rule with `{ "max": 2 }` option:
+
+```js
+/*eslint max-depth: ["error", 2]*/
+
+function foo() {
+ if (true) { // Nested 1 deep
+ class C {
+ static {
+ if (true) { // Nested 1 deep
+ if (true) { // Nested 2 deep
+ if (true) { // Nested 3 deep
+ }
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+Examples of **correct** code for this rule with `{ "max": 2 }` option:
+
+```js
+/*eslint max-depth: ["error", 2]*/
+
+function foo() {
+ if (true) { // Nested 1 deep
+ class C {
+ static {
+ if (true) { // Nested 1 deep
+ if (true) { // Nested 2 deep
+ }
+ }
+ }
+ }
+ }
+}
+```
+
## Related Rules
* [complexity](complexity.md)
}
```
+Note that this rule does not apply to class static blocks, and that statements in class static blocks do not count as statements in the enclosing function.
+
+Examples of **correct** code for this rule with `{ "max": 2 }` option:
+
+```js
+/*eslint max-statements: ["error", 2]*/
+
+function foo() {
+ let one;
+ let two = class {
+ static {
+ let three;
+ let four;
+ let five;
+ if (six) {
+ let seven;
+ let eight;
+ let nine;
+ }
+ }
+ };
+}
+```
+
### ignoreTopLevelFunctions
Examples of additional **correct** code for this rule with the `{ "max": 10 }, { "ignoreTopLevelFunctions": true }` options:
### newIsCapExceptionPattern
-Examples of additional **correct** code for this rule with the `{ "newIsCapExceptionPattern": "^person\.." }` option:
+Examples of additional **correct** code for this rule with the `{ "newIsCapExceptionPattern": "^person\\.." }` option:
```js
-/*eslint new-cap: ["error", { "newIsCapExceptionPattern": "^person\.." }]*/
+/*eslint new-cap: ["error", { "newIsCapExceptionPattern": "^person\\.." }]*/
var friend = new person.acquaintance();
+
var bestFriend = new person.friend();
```
+Examples of additional **correct** code for this rule with the `{ "newIsCapExceptionPattern": "\\.bar$" }` option:
+
+```js
+/*eslint new-cap: ["error", { "newIsCapExceptionPattern": "\\.bar$" }]*/
+
+var friend = new person.bar();
+```
+
### capIsNewExceptions
Examples of additional **correct** code for this rule with the `{ "capIsNewExceptions": ["Person"] }` option:
### capIsNewExceptionPattern
-Examples of additional **correct** code for this rule with the `{ "capIsNewExceptionPattern": "^Person\.." }` option:
+Examples of additional **correct** code for this rule with the `{ "capIsNewExceptionPattern": "^person\\.." }` option:
```js
-/*eslint new-cap: ["error", { "capIsNewExceptionPattern": "^Person\.." }]*/
+/*eslint new-cap: ["error", { "capIsNewExceptionPattern": "^person\\.." }]*/
var friend = person.Acquaintance();
var bestFriend = person.Friend();
```
+Examples of additional **correct** code for this rule with the `{ "capIsNewExceptionPattern": "\\.Bar$" }` option:
+
+```js
+/*eslint new-cap: ["error", { "capIsNewExceptionPattern": "\\.Bar$" }]*/
+
+foo.Bar();
+```
+
+Examples of additional **correct** code for this rule with the `{ "capIsNewExceptionPattern": "^Foo" }` option:
+
+```js
+/*eslint new-cap: ["error", { "capIsNewExceptionPattern": "^Foo" }]*/
+
+var x = Foo(42);
+
+var y = Foobar(42);
+
+var z = Foo.Bar(42);
+```
+
### properties
Examples of **incorrect** code for this rule with the default `{ "properties": true }` option:
## Rule Details
-This rule disallows calls to methods of the `console` object.
+This rule disallows calls or assignments to methods of the `console` object.
Examples of **incorrect** code for this rule:
console.log("Log a debug level message.");
console.warn("Log a warn level message.");
console.error("Log an error level message.");
+console.log = foo();
```
Examples of **correct** code for this rule:
```js
/*eslint no-dupe-class-members: "error"*/
-/*eslint-env es6*/
class Foo {
bar() { }
get bar() { }
}
+class Foo {
+ bar;
+ bar;
+}
+
+class Foo {
+ bar;
+ bar() { }
+}
+
class Foo {
static bar() { }
static bar() { }
```js
/*eslint no-dupe-class-members: "error"*/
-/*eslint-env es6*/
class Foo {
bar() { }
set bar(value) { }
}
+class Foo {
+ bar;
+ qux;
+}
+
+class Foo {
+ bar;
+ qux() { }
+}
+
class Foo {
static bar() { }
bar() { }
baz();
}
```
+
+## Compatibility
+
+* **JSHint**: This rule corresponds to `eqnull` rule of JSHint.
+
+## When Not To Use It
+
+If you want to enforce type-checking operations in general, use the more powerful [eqeqeq](./eqeqeq) instead.
eval() {
}
+
+ static {
+ // This is a user-defined static method.
+ this.eval("var a = 0");
+ }
+
+ static eval() {
+ }
}
```
typeof (a);
(function(){} ? a() : b());
+
+class A {
+ [(x)] = 1;
+}
+
+class B {
+ x = (y + z);
+}
```
Examples of **correct** code for this rule with the default `"all"` option:
for (a in b, c);
for (a in b);
+
+class A {
+ [x] = 1;
+}
+
+class B {
+ x = y + z;
+}
```
### conditionalAssign
// code
};
+class C {
+ field;;
+
+ method() {
+ // code
+ };
+
+ static {
+ // code
+ };
+};
```
Examples of **correct** code for this rule:
var x = 5;
-var foo = function() {
+function foo() {
+ // code
+}
+
+var bar = function() {
// code
};
+class C {
+ field;
+
+ method() {
+ // code
+ }
+
+ static {
+ // code
+ }
+}
```
## When Not To Use It
case 2:
doSomethingElse();
}
+
+switch(foo) {
+ case 1: {
+ doSomething();
+ // falls through
+ }
+
+ case 2: {
+ doSomethingElse();
+ }
+}
```
In this example, there is no confusion as to the expected behavior. It is clear that the first case is meant to fall through to the second case.
case 2:
doSomething();
}
+
+switch(foo) {
+ case 1: {
+ doSomething();
+ // falls through
+ }
+
+ case 2: {
+ doSomethingElse();
+ }
+}
```
Note that the last `case` statement in these examples does not cause a warning because there is nothing to fall through into.
### Global variable leaks
When the code is not in `strict` mode, an assignment to an undeclared variable creates
-a new global variable. This will happen even is the code is in a function.
+a new global variable. This will happen even if the code is in a function.
This does not apply to ES modules since the module code is implicitly in `strict` mode.
mod = 1 // ERROR: 'mod' is readonly.
named = 2 // ERROR: 'named' is readonly.
-mod_ns.named = 3 // ERROR: the members of 'mod_ns' is readonly.
+mod_ns.named = 3 // ERROR: The members of 'mod_ns' are readonly.
mod_ns = {} // ERROR: 'mod_ns' is readonly.
+// Can't extend 'mod_ns'
+Object.assign(mod_ns, { foo: "foo" }) // ERROR: The members of 'mod_ns' are readonly.
```
Examples of **correct** code for this rule:
-# disallow variable or `function` declarations in nested blocks (no-inner-declarations)
+# disallow variable or `function` declarations in nested blocks (no-inner-declarations)
In JavaScript, prior to ES6, a function declaration is only allowed in the first level of a program or the body of another function, though parsers sometimes [erroneously accept them elsewhere](https://code.google.com/p/esprima/issues/detail?id=422). This only applies to function declarations; named or anonymous function expressions can occur anywhere an expression is permitted.
## Rule Details
-This rule requires that function declarations and, optionally, variable declarations be in the root of a program or the body of a function.
+This rule requires that function declarations and, optionally, variable declarations be in the root of a program, or in the root of the body of a function, or in the root of the body of a class static block.
## Options
}
if (foo) function f(){}
+
+class C {
+ static {
+ if (test) {
+ function doSomething() { }
+ }
+ }
+}
```
Examples of **correct** code for this rule with the default `"functions"` option:
function doAnotherThing() { }
}
+class C {
+ static {
+ function doSomething() { }
+ }
+}
+
if (test) {
asyncCall(id, function (err, data) { });
}
}
}
-
if (foo) var a;
if (foo) function f(){}
+
+class C {
+ static {
+ if (test) {
+ var something;
+ }
+ }
+}
```
Examples of **correct** code for this rule with the `"both"` option:
```js
/*eslint no-inner-declarations: ["error", "both"]*/
-/*eslint-env es6*/
var bar = 42;
function doAnotherThing() {
var baz = 81;
}
+
+class C {
+ static {
+ var something;
+ }
+}
```
## When Not To Use It
* The function is on an object literal.
* The function is assigned to a property.
-* The function is a method/getter/setter of ES2015 Classes. (excepts static methods)
+* The function is a method/getter/setter of ES2015 Classes.
And this rule allows `this` keywords in functions below:
* The function is a callback of array methods (such as `.forEach()`) if `thisArg` is given.
* The function has `@this` tag in its JSDoc comment.
+And this rule always allows `this` keywords in the following contexts:
+
+* In class field initializers.
+* In class static blocks.
+
Otherwise are considered problems.
This rule applies **only** in strict mode.
};
class Foo {
+
+ // OK, this is in a class field initializer.
+ a = this.b;
+
+ // OK, static initializers also have valid this.
+ static a = this.b;
+
foo() {
// OK, this is in a method.
this.a = 0;
this.a = 0;
baz(() => this);
}
+
+ static {
+ // OK, static blocks also have valid this.
+ this.a = 0;
+ baz(() => this);
+ }
}
var foo = (function foo() {
aLabel: {
}
}
+
+class C {
+ static {
+ {
+ foo();
+ }
+ }
+}
```
Examples of **correct** code for this rule with ES6 environment:
aLabel: {
}
+
+class C {
+ static {
+ lbl: {
+ if (something) {
+ break lbl;
+ }
+
+ foo();
+ }
+ }
+}
```
Examples of **correct** code for this rule with ES6 environment and strict mode via `"parserOptions": { "sourceType": "module" }` in the ESLint configuration or `"use strict"` directive in the code:
let a =
b =
c;
+
+class Foo {
+ a = b = 10;
+}
+
+a = b = "quux";
```
Examples of **correct** code for this rule:
```js
/*eslint no-multi-assign: "error"*/
+
var a = 5;
var b = 5;
var c = 5;
let a = c;
let b = c;
+
+class Foo {
+ a = 10;
+ b = 10;
+}
+
+a = "quux";
+b = "quux";
```
## Options
This rule has an object option:
-* `"ignoreNonDeclaration"`: When set to `true`, the rule allows chains that don't include initializing a variable in a declaration. Default is `false`.
+* `"ignoreNonDeclaration"`: When set to `true`, the rule allows chains that don't include initializing a variable in a declaration or initializing a class field. Default is `false`.
### ignoreNonDeclaration
let a = b = "baz";
const foo = bar = 1;
+
+class Foo {
+ a = b = 10;
+}
```
## Related Rules
# Disallow Function Constructor (no-new-func)
-It's possible to create functions in JavaScript using the `Function` constructor, such as:
+It's possible to create functions in JavaScript from strings at runtime using the `Function` constructor, such as:
```js
var x = new Function("a", "b", "return a + b");
+var x = Function("a", "b", "return a + b");
+var x = Function.call(null, "a", "b", "return a + b");
+var x = Function.apply(null, ["a", "b", "return a + b"]);
+var x = Function.bind(null, "a", "b", "return a + b")();
```
-This is considered by many to be a bad practice due to the difficulty in debugging and reading these types of functions.
+This is considered by many to be a bad practice due to the difficulty in debugging and reading these types of functions. In addition, Content-Security-Policy (CSP) directives may disallow the use of eval() and similar methods for creating code from strings.
## Rule Details
var x = new Function("a", "b", "return a + b");
var x = Function("a", "b", "return a + b");
+var x = Function.call(null, "a", "b", "return a + b");
+var x = Function.apply(null, ["a", "b", "return a + b"]);
+var x = Function.bind(null, "a", "b", "return a + b")();
+var f = Function.bind(null, "a", "b", "return a + b"); // assuming that the result of Function.bind(...) will be eventually called.
```
Examples of **correct** code for this rule:
var a = 3;
var a = 10;
+
+class C {
+ foo() {
+ var b = 3;
+ var b = 10;
+ }
+
+ static {
+ var c = 3;
+ var c = 10;
+ }
+}
```
Examples of **correct** code for this rule:
/*eslint no-redeclare: "error"*/
var a = 3;
-// ...
a = 10;
+
+class C {
+ foo() {
+ var b = 3;
+ b = 10;
+ }
+
+ static {
+ var c = 3;
+ c = 10;
+ }
+}
+
```
## Options
## Rule Details
-This rule aims to eliminate variable declarations that initialize to `undefined`.
+This rule aims to eliminate `var` and `let` variable declarations that initialize to `undefined`.
Examples of **incorrect** code for this rule:
```js
/*eslint no-undef-init: "error"*/
-/*eslint-env es6*/
var foo = undefined;
let bar = undefined;
```js
/*eslint no-undef-init: "error"*/
-/*eslint-env es6*/
var foo;
let bar;
-const baz = undefined;
+```
+
+Please note that this rule does not check `const` declarations, destructuring patterns, function parameters, and class fields.
+
+Examples of additional **correct** code for this rule:
+
+```js
+/*eslint no-undef-init: "error"*/
+
+const foo = undefined;
+
+let { bar = undefined } = baz;
+
+[quux = undefined] = quuux;
+
+(foo = undefined) => {};
+
+class Foo {
+ bar = undefined;
+}
```
## When Not To Use It
}
```
+Another kind of mistake is defining instance fields in a subclass whose constructor doesn't call `super()`. Instance fields of a subclass are only added to the instance after `super()`. If there are no `super()` calls, their definitions are never applied and therefore are unreachable code.
+
+```js
+class C extends B {
+ #x; // this will never be added to instances
+
+ constructor() {
+ return {};
+ }
+}
+```
+
## Rule Details
-This rule disallows unreachable code after `return`, `throw`, `continue`, and `break` statements.
+This rule disallows unreachable code after `return`, `throw`, `continue`, and `break` statements. This rule also flags definitions of instance fields in subclasses whose constructors don't have `super()` calls.
Examples of **incorrect** code for this rule:
var x;
}
```
+
+Examples of additional **incorrect** code for this rule:
+
+```js
+/*eslint no-unreachable: "error"*/
+
+class C extends B {
+ #x; // unreachable
+ #y = 1; // unreachable
+ a; // unreachable
+ b = 1; // unreachable
+
+ constructor() {
+ return {};
+ }
+}
+```
+
+Examples of additional **correct** code for this rule:
+
+```js
+/*eslint no-unreachable: "error"*/
+
+class D extends B {
+ #x;
+ #y = 1;
+ a;
+ b = 1;
+
+ constructor() {
+ super();
+ }
+}
+
+class E extends B {
+ #x;
+ #y = 1;
+ a;
+ b = 1;
+
+ // implicit constructor always calls `super()`
+}
+
+class F extends B {
+ static #x;
+ static #y = 1;
+ static a;
+ static b = 1;
+
+ constructor() {
+ return {};
+ }
+}
+```
```
-Note that one or more string expression statements (with or without semi-colons) will only be considered as unused if they are not in the beginning of a script, module, or function (alone and uninterrupted by other statements). Otherwise, they will be treated as part of a "directive prologue", a section potentially usable by JavaScript engines. This includes "strict mode" directives.
-
-```js
-"use strict";
-"use asm"
-"use stricter";
-"use babel"
-"any other strings like this in the prologue";
-```
-
Examples of **correct** code for the default `{ "allowShortCircuit": false, "allowTernary": false }` options:
```js
void a
```
+Note that one or more string expression statements (with or without semi-colons) will only be considered as unused if they are not in the beginning of a script, module, or function (alone and uninterrupted by other statements). Otherwise, they will be treated as part of a "directive prologue", a section potentially usable by JavaScript engines. This includes "strict mode" directives.
+
+Examples of **correct** code for this rule in regard to directives:
+
+```js
+/*eslint no-unused-expressions: "error"*/
+
+"use strict";
+"use asm"
+"use stricter";
+"use babel"
+"any other strings like this in the directive prologue";
+"this is still the directive prologue";
+
+function foo() {
+ "bar";
+}
+
+class Foo {
+ someMethod() {
+ "use strict";
+ }
+}
+```
+
+Examples of **incorrect** code for this rule in regard to directives:
+
+```js
+/*eslint no-unused-expressions: "error"*/
+
+doSomething();
+"use strict"; // this isn't in a directive prologue, because there is a non-directive statement before it
+
+function foo() {
+ "bar" + 1;
+}
+
+class Foo {
+ static {
+ "use strict"; // class static blocks do not have directive prologues
+ }
+}
+```
+
### allowShortCircuit
Examples of **incorrect** code for the `{ "allowShortCircuit": true }` option:
--- /dev/null
+# Disallow Unused Private Class Members (no-unused-private-class-members)
+
+Private class members that are declared and not used anywhere in the code are most likely an error due to incomplete refactoring. Such class members take up space in the code and can lead to confusion by readers.
+
+## Rule Details
+
+This rule reports unused private class members.
+
+* A private field or method is considered to be unused if its value is never read.
+* A private accessor is considered to be unused if it is never accessed (read or write).
+
+Examples of **incorrect** code for this rule:
+
+```js
+/*eslint no-unused-private-class-members: "error"*/
+
+class Foo {
+ #unusedMember = 5;
+}
+
+class Foo {
+ #usedOnlyInWrite = 5;
+ method() {
+ this.#usedOnlyInWrite = 42;
+ }
+}
+
+class Foo {
+ #usedOnlyToUpdateItself = 5;
+ method() {
+ this.#usedOnlyToUpdateItself++;
+ }
+}
+
+class Foo {
+ #unusedMethod() {}
+}
+
+class Foo {
+ get #unusedAccessor() {}
+ set #unusedAccessor(value) {}
+}
+```
+
+Examples of **correct** code for this rule:
+
+```js
+/*eslint no-unused-private-class-members: "error"*/
+
+class Foo {
+ #usedMember = 42;
+ method() {
+ return this.#usedMember;
+ }
+}
+
+class Foo {
+ #usedMethod() {
+ return 42;
+ }
+ anotherMethod() {
+ return this.#usedMethod();
+ }
+}
+
+class Foo {
+ get #usedAccessor() {}
+ set #usedAccessor(value) {}
+
+ method() {
+ this.#usedAccessor = 42;
+ }
+}
+```
+
+## When Not To Use It
+
+If you don't want to be notified about unused private class members, you can safely turn this rule off.
```js
/*eslint no-use-before-define: "error"*/
-/*eslint-env es6*/
alert(a);
var a = 10;
alert(c);
let c = 1;
}
+
+{
+ class C extends C {}
+}
+
+{
+ class C {
+ static x = "foo";
+ [C.x]() {}
+ }
+}
+
+{
+ const C = class {
+ static x = C;
+ }
+}
+
+{
+ const C = class {
+ static {
+ C.x = "foo";
+ }
+ }
+}
```
Examples of **correct** code for this rule:
```js
/*eslint no-use-before-define: "error"*/
-/*eslint-env es6*/
var a;
a = 10;
let c;
c++;
}
+
+{
+ class C {
+ static x = C;
+ }
+}
+
+{
+ const C = class C {
+ static x = C;
+ }
+}
+
+{
+ const C = class {
+ x = C;
+ }
+}
+
+{
+ const C = class C {
+ static {
+ C.x = "foo";
+ }
+ }
+}
```
## Options
```json
{
- "no-use-before-define": ["error", { "functions": true, "classes": true }]
+ "no-use-before-define": ["error", { "functions": true, "classes": true, "variables": true }]
}
```
```js
/*eslint no-use-before-define: ["error", { "classes": false }]*/
-/*eslint-env es6*/
new A();
class A {
}
+
+{
+ class C extends C {}
+}
+
+{
+ class C extends D {}
+ class D {}
+}
+
+{
+ class C {
+ static x = "foo";
+ [C.x]() {}
+ }
+}
+
+{
+ class C {
+ static {
+ new D();
+ }
+ }
+ class D {}
+}
```
Examples of **correct** code for the `{ "classes": false }` option:
```js
/*eslint no-use-before-define: ["error", { "classes": false }]*/
-/*eslint-env es6*/
function foo() {
return new A();
g();
const g = function() {};
+
+{
+ const C = class {
+ static x = C;
+ }
+}
+
+{
+ const C = class {
+ static x = foo;
+ }
+ const foo = 1;
+}
+
+{
+ class C {
+ static {
+ this.x = foo;
+ }
+ }
+ const foo = 1;
+}
```
Examples of **correct** code for the `{ "variables": false }` option:
const e = function() { return g(); }
const g = function() {}
+
+{
+ const C = class {
+ x = foo;
+ }
+ const foo = 1;
+}
```
```js
/*eslint no-useless-computed-key: "error"*/
-/*eslint-env es6*/
var a = { ['0']: 0 };
var a = { ['0+1,234']: 0 };
var c = { '0+1,234': 0 };
```
+Examples of additional **correct** code for this rule:
+
+```js
+/*eslint no-useless-computed-key: "error"*/
+
+var c = {
+ "__proto__": foo, // defines object's prototype
+
+ ["__proto__"]: bar // defines a property named "__proto__"
+};
+```
+
## Options
This rule has an object option:
By default, this rule does not check class declarations and class expressions,
as the default value for `enforceForClassMembers` is `false`.
-When `enforceForClassMembers` is set to `true`, the rule will also disallow unnecessary computed
-keys inside of class methods, getters and setters.
+When `enforceForClassMembers` is set to `true`, the rule will also disallow unnecessary computed keys inside of class fields, class methods, class getters, and class setters.
-Examples of **incorrect** code for `{ "enforceForClassMembers": true }`:
+Examples of **incorrect** code for this rule with the `{ "enforceForClassMembers": true }` option:
```js
/*eslint no-useless-computed-key: ["error", { "enforceForClassMembers": true }]*/
class Foo {
+ ["foo"] = "bar";
+
[0]() {}
['a']() {}
get ['b']() {}
set ['c'](value) {}
+ static ["foo"] = "bar";
+
static ['a']() {}
}
```
+Examples of **correct** code for this rule with the `{ "enforceForClassMembers": true }` option:
+
+```js
+/*eslint no-useless-computed-key: ["error", { "enforceForClassMembers": true }]*/
+
+class Foo {
+ "foo" = "bar";
+
+ 0() {}
+ 'a'() {}
+ get 'b'() {}
+ set 'c'(value) {}
+
+ static "foo" = "bar";
+
+ static 'a'() {}
+}
+```
+
+Examples of additional **correct** code for this rule with the `{ "enforceForClassMembers": true }` option:
+
+```js
+/*eslint no-useless-computed-key: ["error", { "enforceForClassMembers": true }]*/
+
+class Foo {
+ ["constructor"]; // instance field named "constructor"
+
+ "constructor"() {} // the constructor of this class
+
+ ["constructor"]() {} // method named "constructor"
+
+ static ["constructor"]; // static field named "constructor"
+
+ static ["prototype"]; // runtime error, it would be a parsing error without `[]`
+}
+```
+
## When Not To Use It
If you don't want to be notified about unnecessary computed property keys, you can safely disable this rule.
```js
/*eslint one-var: ["error", "always"]*/
-/*eslint-env es6*/
function foo() {
var bar;
var qux = true;
}
}
+
+class C {
+ static {
+ var foo;
+ var bar;
+ }
+
+ static {
+ var foo;
+ if (bar) {
+ var baz = true;
+ }
+ }
+
+ static {
+ let foo;
+ let bar;
+ }
+}
```
Examples of **correct** code for this rule with the default `"always"` option:
```js
/*eslint one-var: ["error", "always"]*/
-/*eslint-env es6*/
function foo() {
var bar,
let qux;
}
}
+
+class C {
+ static {
+ var foo, bar;
+ }
+
+ static {
+ var foo, baz;
+ if (bar) {
+ baz = true;
+ }
+ }
+
+ static {
+ let foo, bar;
+ }
+
+ static {
+ let foo;
+ if (bar) {
+ let baz;
+ }
+ }
+}
```
### never
```js
/*eslint one-var: ["error", "never"]*/
-/*eslint-env es6*/
function foo() {
var bar,
let bar = true,
baz = false;
}
+
+class C {
+ static {
+ var foo, bar;
+ let baz, qux;
+ }
+}
```
Examples of **correct** code for this rule with the `"never"` option:
```js
/*eslint one-var: ["error", "never"]*/
-/*eslint-env es6*/
function foo() {
var bar;
let qux = true;
}
}
+
+class C {
+ static {
+ var foo;
+ var bar;
+ let baz;
+ let qux;
+ }
+}
```
### consecutive
```js
/*eslint one-var: ["error", "consecutive"]*/
-/*eslint-env es6*/
function foo() {
var bar;
var qux = 3;
var quux;
}
+
+class C {
+ static {
+ var foo;
+ var bar;
+ let baz;
+ let qux;
+ }
+}
```
Examples of **correct** code for this rule with the `"consecutive"` option:
```js
/*eslint one-var: ["error", "consecutive"]*/
-/*eslint-env es6*/
-
function foo() {
var bar,
var qux = 3,
quux;
}
+
+class C {
+ static {
+ var foo, bar;
+ let baz, qux;
+ doSomething();
+ let quux;
+ var quuux;
+ }
+}
```
### var, let, and const
answer = everything
? 42
: foo;
+
+class Foo {
+ a
+ = 1;
+ [b]
+ = 2;
+ [c
+ ]
+ = 3;
+}
```
Examples of **correct** code for this rule with the `"after"` option:
answer = everything ?
42 :
foo;
+
+class Foo {
+ a =
+ 1;
+ [b] =
+ 2;
+ [c
+ ] =
+ 3;
+ d = 4;
+}
```
### before
answer = everything ?
42 :
foo;
+
+class Foo {
+ a =
+ 1;
+ [b] =
+ 2;
+ [c
+ ] =
+ 3;
+}
```
Examples of **correct** code for this rule with the `"before"` option:
answer = everything
? 42
: foo;
+
+class Foo {
+ a
+ = 1;
+ [b]
+ = 2;
+ [c
+ ]
+ = 3;
+ d = 4;
+}
```
### none
answer = everything ?
42 :
foo;
+
+class Foo {
+ a =
+ 1;
+ [b] =
+ 2;
+ [c
+ ] =
+ 3;
+ d
+ = 4;
+ [e]
+ = 5;
+ [f
+ ]
+ = 6;
+}
```
Examples of **correct** code for this rule with the `"none"` option:
}
answer = everything ? 42 : foo;
+
+class Foo {
+ a = 1;
+ [b] = 2;
+ [c
+ ] = 3;
+ d = 4;
+ [e] = 5;
+ [f
+ ] = 6;
+}
```
### overrides
String option:
-* `"always"` (default) requires empty lines at the beginning and ending of block statements and classes
-* `"never"` disallows empty lines at the beginning and ending of block statements and classes
+* `"always"` (default) requires empty lines at the beginning and ending of block statements, function bodies, class static blocks, classes, and `switch` statements.
+* `"never"` disallows empty lines at the beginning and ending of block statements, function bodies, class static blocks, classes, and `switch` statements.
Object option:
-* `"blocks"` require or disallow padding within block statements
+* `"blocks"` require or disallow padding within block statements, function bodies, and class static blocks
* `"classes"` require or disallow padding within classes
* `"switches"` require or disallow padding within `switch` statements
b();
}
+
+class C {
+ static {
+ a();
+ }
+}
```
Examples of **correct** code for this rule with the default `"always"` option:
// comment
b();
+}
+
+class C {
+
+ static {
+
+ a();
+
+ }
+
}
```
if (a) {
b();
+}
+
+class C {
+
+ static {
+
+ a();
+
+ }
+
}
```
{
b();
}
+
+class C {
+ static {
+ a();
+ }
+}
```
### blocks
// comment
b();
+}
+
+class C {
+
+ static {
+ a();
+ }
+
}
```
// comment
b();
+}
+
+class C {
+
+ static {
+
+ a();
+
+ }
+
+}
+
+class D {
+ static {
+
+ a();
+
+ }
+
}
```
b();
}
+
+class C {
+ static {
+
+ a();
+
+ }
+}
```
Examples of **correct** code for this rule with the `{ "blocks": "never" }` option:
{
b();
}
+
+class C {
+ static {
+ a();
+ }
+}
+
+class D {
+
+ static {
+ a();
+ }
+
+}
```
### classes
const a = 0;
bar();
}
+
+class C {
+ static {
+ let a = 0;
+ bar();
+ }
+}
```
Examples of **correct** code for the `[{ blankLine: "always", prev: ["const", "let", "var"], next: "*"}, { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"]}]` configuration:
bar();
}
+
+class C {
+ static {
+ let a = 0;
+ let b = 0;
+
+ bar();
+ }
+}
```
----
```js
/*eslint prefer-const: "error"*/
-/*eslint-env es6*/
// it's initialized and never reassigned.
let a = 3;
a = 0;
console.log(a);
+class C {
+ static {
+ let a;
+ a = 0;
+ console.log(a);
+ }
+}
+
// `i` is redefined (not reassigned) on each loop step.
for (let i in [1, 2, 3]) {
console.log(i);
```js
/*eslint prefer-const: "error"*/
-/*eslint-env es6*/
// using const.
const a = 0;
}
console.log(a);
+// it's initialized in a different scope.
+let a;
+class C {
+ #x;
+ static {
+ a = obj => obj.#x;
+ }
+}
+
// it's initialized at a place that we cannot write a variable declaration.
let a;
if (true) a = 0;
var { bar: foo } = object;
```
+Examples of additional **correct** code when `enforceForRenamedProperties` is enabled:
+
+```javascript
+class C {
+ #x;
+ foo() {
+ const bar = this.#x; // private identifiers are not allowed in destructuring
+ }
+}
+```
+
An example configuration, with the defaults `array` and `object` filled in, looks like this:
```json
# Suggest using named capture group in regular expression (prefer-named-capture-group)
+
+## Rule Details
+
With the landing of ECMAScript 2018, named capture groups can be used in regular expressions, which can improve their readability.
+This rule is aimed at using named capture groups instead of numbered capture groups in regular expressions:
```js
const regex = /(?<year>[0-9]{4})/;
```
-## Rule Details
+Alternatively, if your intention is not to _capture_ the results, but only express the alternative, use a non-capturing group:
-This rule is aimed at using named capture groups instead of numbered capture groups in regular expressions.
+```js
+const regex = /(?:cauli|sun)flower/;
+```
Examples of **incorrect** code for this rule:
const foo = /(?<id>ba[rz])/;
const bar = new RegExp('(?<id>ba[rz])');
const baz = RegExp('(?<id>ba[rz])');
+const xyz = /xyz(?:zy|abc)/;
foo.exec('bar').groups.id; // Retrieve the group result.
```
## When Not To Use It
-If you are targeting ECMAScript 2017 and/or older environments, you can disable this rule, because this ECMAScript feature is only supported in ECMAScript 2018 and/or newer environments.
+If you are targeting ECMAScript 2017 and/or older environments, you should not use this rule, because this ECMAScript feature is only supported in ECMAScript 2018 and/or newer environments.
## Related Rules
## Rule Details
-This rule aims to report assignments to variables or properties where all of the following are true:
+This rule aims to report assignments to variables or properties in cases where the assignments may be based on outdated values.
-* A variable or property is reassigned to a new value which is based on its old value.
-* A `yield` or `await` expression interrupts the assignment after the old value is read, and before the new value is set.
-* The rule cannot easily verify that the assignment is safe (e.g. if an assigned variable is local and would not be readable from anywhere else while the function is paused).
+### Variables
+
+This rule reports an assignment to a variable when it detects the following execution flow in a generator or async function:
+
+1. The variable is read.
+2. A `yield` or `await` pauses the function.
+3. After the function is resumed, a value is assigned to the variable from step 1.
+
+The assignment in step 3 is reported because it may be incorrectly resolved because the value of the variable from step 1 may have changed between steps 2 and 3. In particular, if the variable can be accessed from other execution contexts (for example, if it is not a local variable and therefore other functions can change it), the value of the variable may have changed elsewhere while the function was paused in step 2.
+
+Note that the rule does not report the assignment in step 3 in any of the following cases:
+
+* If the variable is read again between steps 2 and 3.
+* If the variable cannot be accessed while the function is paused (for example, if it's a local variable).
Examples of **incorrect** code for this rule:
/* eslint require-atomic-updates: error */
let result;
-async function foo() {
- result += await somethingElse;
- result = result + await somethingElse;
+async function foo() {
+ result += await something;
+}
- result = result + doSomething(await somethingElse);
+async function bar() {
+ result = result + await something;
}
-function* bar() {
- result += yield;
+async function baz() {
+ result = result + doSomething(await somethingElse);
+}
- result = result + (yield somethingElse);
+async function qux() {
+ if (!result) {
+ result = await initialize();
+ }
+}
- result = result + doSomething(yield somethingElse);
+function* generator() {
+ result += yield;
}
```
/* eslint require-atomic-updates: error */
let result;
-async function foo() {
- result = await somethingElse + result;
- let tmp = await somethingElse;
- result += tmp;
+async function foobar() {
+ result = await something + result;
+}
+
+async function baz() {
+ const tmp = doSomething(await somethingElse);
+ result += tmp;
+}
+
+async function qux() {
+ if (!result) {
+ const tmp = await initialize();
+ if (!result) {
+ result = tmp;
+ }
+ }
+}
+
+async function quux() {
+ let localVariable = 0;
+ localVariable += await something;
+}
+
+function* generator() {
+ result = (yield) + result;
+}
+```
+
+### Properties
+
+This rule reports an assignment to a property through a variable when it detects the following execution flow in a generator or async function:
+
+1. The variable or object property is read.
+2. A `yield` or `await` pauses the function.
+3. After the function is resumed, a value is assigned to a property.
- let localVariable = 0;
- localVariable += await somethingElse;
+This logic is similar to the logic for variables, but stricter because the property in step 3 doesn't have to be the same as the property in step 1. It is assumed that the flow depends on the state of the object as a whole.
+
+Example of **incorrect** code for this rule:
+
+```js
+/* eslint require-atomic-updates: error */
+
+async function foo(obj) {
+ if (!obj.done) {
+ obj.something = await getSomething();
+ }
}
+```
-function* bar() {
- result = (yield) + result;
+Example of **correct** code for this rule:
- result = (yield somethingElse) + result;
+```js
+/* eslint require-atomic-updates: error */
+
+async function foo(obj) {
+ if (!obj.done) {
+ const tmp = await getSomething();
+ if (!obj.done) {
+ obj.something = tmp;
+ }
+ }
+}
+```
+
+## Options
+
+This rule has an object option:
+
+* `"allowProperties"`: When set to `true`, the rule does not report assignments to properties. Default is `false`.
+
+### allowProperties
+
+Example of **correct** code for this rule with the `{ "allowProperties": true }` option:
+
+```js
+/* eslint require-atomic-updates: ["error", { "allowProperties": true }] */
- result = doSomething(yield somethingElse, result);
+async function foo(obj) {
+ if (!obj.done) {
+ obj.something = await getSomething();
+ }
}
```
) {
foo()
}
+
+class C {
+ static {
+ foo()
+ ;bar()
+ }
+}
```
Examples of **correct** code for this rule with `"last"` option:
) {
foo()
}
+
+class C {
+ static {
+ foo();
+ bar()
+ }
+}
```
Examples of **incorrect** code for this rule with `"first"` option:
) {
foo()
}
+
+class C {
+ static {
+ foo();
+ bar()
+ }
+}
```
Examples of **correct** code for this rule with `"first"` option:
) {
foo()
}
+
+class C {
+ static {
+ foo()
+ ;bar()
+ }
+}
```
## When Not To Use It
* `"beforeStatementContinuationChars": "always"` requires semicolons at the end of statements if the next line starts with `[`, `(`, `/`, `+`, or `-`.
* `"beforeStatementContinuationChars": "never"` disallows semicolons as the end of statements if it doesn't make ASI hazard even if the next line starts with `[`, `(`, `/`, `+`, or `-`.
+**Note:** `beforeStatementContinuationChars` does not apply to class fields because class fields are not statements.
+
### always
Examples of **incorrect** code for this rule with the default `"always"` option:
object.method = function() {
// ...
}
+
+class Foo {
+ bar = 1
+}
```
Examples of **correct** code for this rule with the default `"always"` option:
object.method = function() {
// ...
};
+
+class Foo {
+ bar = 1;
+}
```
### never
object.method = function() {
// ...
};
+
+class Foo {
+ bar = 1;
+}
```
Examples of **correct** code for this rule with the `"never"` option:
;(function() {
// ...
})()
+
+class Foo {
+ bar = 1
+}
```
#### omitLastInOneLineBlock
if (foo) { bar() }
if (foo) { bar(); baz() }
+
+function f() { bar(); baz() }
+
+class C {
+ foo() { bar(); baz() }
+
+ static { bar(); baz() }
+}
```
#### beforeStatementContinuationChars
(removed) This rule was **removed** in ESLint v2.0 and replaced by the [keyword-spacing](keyword-spacing.md) rule.
-(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fix) automatically fixed problems reported by this rule.
+(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#--fix) automatically fixed problems reported by this rule.
Some style guides will require or disallow spaces following the certain keywords.
* This rule ignores spacing which is between `=>` and a block. The spacing is handled by the `arrow-spacing` rule.
* This rule ignores spacing which is between a keyword and a block. The spacing is handled by the `keyword-spacing` rule.
+* This rule ignores spacing which is between `:` of a switch case and a block. The spacing is handled by the `switch-colon-spacing` rule.
## Options
c();
}
+class C {
+ static{} /*no error. this is checked by `keyword-spacing` rule.*/
+}
function a() {}
* [keyword-spacing](keyword-spacing.md)
* [arrow-spacing](arrow-spacing.md)
+* [switch-colon-spacing](switch-colon-spacing.md)
* [brace-style](brace-style.md)
(removed) This rule was **removed** in ESLint v2.0 and **replaced** by the [keyword-spacing](keyword-spacing.md) rule.
-(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fix) automatically fixed problems reported by this rule.
+(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#--fix) automatically fixed problems reported by this rule.
Keywords are syntax elements of JavaScript, such as `function` and `if`. These identifiers have special meaning to the language and so often appear in a different color in code editors. As an important part of the language, style guides often refer to the spacing that should be used around keywords. For example, you might have a style guide that says keywords should be always be preceded by spaces, which would mean `if-else` statements must look like this:
(removed) This rule was **removed** in ESLint v2.0 and **replaced** by the [keyword-spacing](keyword-spacing.md) rule.
-(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fix) automatically fixed problems reported by this rule.
+(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#--fix) automatically fixed problems reported by this rule.
Require spaces following `return`, `throw`, and `case`.
This rule disallows strict mode directives, no matter which option is specified, in functions with non-simple parameter lists (for example, parameter lists with default parameter values) because that is a syntax error in **ECMAScript 2016** and later. See the examples of the [function](#function) option.
+This rule does not apply to class static blocks, no matter which option is specified, because class static blocks do not have directives. Therefore, a `"use strict"` statement in a class static block is not a directive, and will be reported by the [no-unused-expressions](no-unused-expressions.md) rule.
+
The `--fix` option on the command line does not insert new `"use strict"` statements, but only removes unneeded statements.
## Options
if (foo != NaN) {
// ...
}
+
+if (foo == Number.NaN) {
+ // ...
+}
+
+if (foo != Number.NaN) {
+ // ...
+}
```
Examples of **correct** code for this rule:
break;
// ...
}
+
+switch (foo) {
+ case Number.NaN:
+ bar();
+ break;
+ case 1:
+ baz();
+ break;
+ // ...
+}
+
+switch (Number.NaN) {
+ case a:
+ bar();
+ break;
+ case b:
+ baz();
+ break;
+ // ...
+}
```
Examples of **correct** code for this rule with `"enforceForSwitchCase"` option set to `true` (default):
break;
// ...
}
+
+switch (foo) {
+ case Number.NaN:
+ bar();
+ break;
+ case 1:
+ baz();
+ break;
+ // ...
+}
+
+switch (Number.NaN) {
+ case a:
+ bar();
+ break;
+ case b:
+ baz();
+ break;
+ // ...
+}
```
### enforceForIndexOf
```js
/*eslint vars-on-top: "error"*/
-// Variable declarations in a block:
+// Variable declaration in a nested block, and a variable declaration after other statements:
function doSomething() {
- var first;
if (true) {
- first = true;
+ var first = true;
}
var second;
}
```js
/*eslint vars-on-top: "error"*/
-// Variables after other statements:
+// Variable declaration after other statements:
f();
var a;
```
+```js
+/*eslint vars-on-top: "error"*/
+
+// Variables in class static blocks should be at the top of the static blocks.
+
+class C {
+
+ // Variable declaration in a nested block:
+ static {
+ if (something) {
+ var a = true;
+ }
+ }
+
+ // Variable declaration after other statements:
+ static {
+ f();
+ var a;
+ }
+
+}
+```
+
Examples of **correct** code for this rule:
```js
```js
/*eslint vars-on-top: "error"*/
+class C {
+
+ static {
+ var a;
+ if (something) {
+ a = true;
+ }
+ }
+
+ static {
+ var a;
+ f();
+ }
+
+}
+```
+
+```js
+/*eslint vars-on-top: "error"*/
+
// Directives may precede variable declarations.
"use strict";
var a;
Fixing problems:
--fix Automatically fix problems
--fix-dry-run Automatically fix problems without saving the changes to the file system
- --fix-type Array Specify the types of fixes to apply (problem, suggestion, layout)
+ --fix-type Array Specify the types of fixes to apply (directive, problem, suggestion, layout)
Ignoring files:
--ignore-path path::String Specify path of ignore file
--init Run config initialization wizard - default: false
--env-info Output execution environment information - default: false
--no-error-on-unmatched-pattern Prevent errors when pattern is unmatched - default: false
+ --exit-on-fatal-error Exit with exit code 2 in case of fatal error - default: false
--debug Output debugging information
-h, --help Show help
-v, --version Output the version number
Examples:
- eslint --rule 'quotes: [2, double]'
- eslint --rule 'guard-for-in: 2' --rule 'brace-style: [2, 1tbs]'
- eslint --rule 'jquery/dollar-sign: 2'
+ eslint --rule 'quotes: [error, double]'
+ eslint --rule 'guard-for-in: error' --rule 'brace-style: [error, 1tbs]'
+ eslint --rule 'jquery/dollar-sign: error'
### Fixing problems
#### `--fix-type`
-This option allows you to specify the type of fixes to apply when using either `--fix` or `--fix-dry-run`. The three types of fixes are:
+This option allows you to specify the type of fixes to apply when using either `--fix` or `--fix-dry-run`. The four types of fixes are:
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`
You can specify one or more fix type on the command line. Here are some examples:
This option specifies the output format for the console. Possible formats are:
* [checkstyle](formatters.md/#checkstyle)
-* [codeframe](formatters.md/#codeframe)
* [compact](formatters.md/#compact)
* [html](formatters.md/#html)
* [jslint-xml](formatters.md/#jslint-xml)
* [json](formatters.md/#json)
* [junit](formatters.md/#junit)
* [stylish](formatters.md/#stylish) (the default)
-* [table](formatters.md/#table)
* [tap](formatters.md/#tap)
* [unix](formatters.md/#unix)
* [visualstudio](formatters.md/#visualstudio)
This option prevents errors when a quoted glob pattern or `--ext` is unmatched. This will not prevent errors when your shell can't match a glob.
+#### `--exit-on-fatal-error`
+
+This option causes ESLint to exit with exit code 2 if one or more fatal parsing errors occur. Without this option, fatal parsing errors are reported as rule violations.
+
#### `--debug`
-This option outputs debugging information to the console. 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.
+This option outputs debugging information to the console. 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.
+Add this flag to an ESLint command line invocation in order to get extra debug information as the command is run (e.g. `eslint --debug test.js` and `eslint test.js --debug` are equivalent)
#### `-h`, `--help`
* [Cascading and Hierarchy](#cascading-and-hierarchy)
* [Extending Configuration Files](#extending-configuration-files)
* [Configuration Based on Glob Patterns](#configuration-based-on-glob-patterns)
-* [Personal Configuration Files](#personal-configuration-files)
+* [Personal Configuration Files](#personal-configuration-files-deprecated)
## Configuration File Formats
eslint -c myconfig.json myfiletotest.js
-If you are using one configuration file and want ESLint to ignore any `.eslintrc.*` files, make sure to use [`--no-eslintrc`](https://eslint.org/docs/user-guide/command-line-interface#-no-eslintrc) along with the [`-c`](https://eslint.org/docs/user-guide/command-line-interface#-c-config) flag.
+If you are using one configuration file and want ESLint to ignore any `.eslintrc.*` files, make sure to use [`--no-eslintrc`](https://eslint.org/docs/user-guide/command-line-interface#--no-eslintrc) along with the [`-c`](https://eslint.org/docs/user-guide/command-line-interface#-c---config) flag.
+
+Here's an example JSON configuration file that uses the `typescript-eslint` parser to support TypeScript syntax:
+
+```json
+{
+ "root": true,
+ "extends": [
+ "eslint:recommended",
+ "plugin:@typescript-eslint/recommended"
+ ],
+ "parser": "@typescript-eslint/parser",
+ "parserOptions": { "project": ["./tsconfig.json"] },
+ "plugins": [
+ "@typescript-eslint"
+ ],
+ "rules": {
+ "@typescript-eslint/strict-boolean-expressions": [
+ 2,
+ {
+ "allowString" : false,
+ "allowNumber" : false
+ }
+ ]
+ },
+ "ignorePatterns": ["src/**/*.test.ts", "src/frontend/generated/*"]
+}
+```
### Comments in configuration files
* 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
You might enable all core rules as a shortcut to explore rules and options while you decide on the configuration for a project, especially if you rarely override options or disable rules. The default options for rules are not endorsements by ESLint (for example, the default option for the [`quotes`](https://eslint.org/docs/rules/quotes) rule does not mean double quotes are better than single quotes).
-If your configuration extends `eslint:all`, after you upgrade to a newer major or minor version of ESLint, review the reported problems before you use the `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix), so you know if a new fixable rule will make changes to the code.
+If your configuration extends `eslint:all`, after you upgrade to a newer major or minor version of ESLint, review the reported problems before you use the `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#--fix), so you know if a new fixable rule will make changes to the code.
Example of a configuration file in JavaScript format:
Parser options are set in your `.eslintrc.*` file by using the `parserOptions` property. The available options are:
-* `ecmaVersion` - set to 3, 5 (default), 6, 7, 8, 9, 10, 11, or 12 to specify the version of ECMAScript syntax you want to use. You can also set 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), or 2021 (same as 12) to use the year-based naming.
+* `ecmaVersion` - set to 3, 5 (default), 6, 7, 8, 9, 10, 11, 12, or 13 to specify the version of ECMAScript syntax you want to use. You can also set 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), or 2022 (same as 13) 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.
* `ecmaFeatures` - an object indicating which additional language features you'd like to use:
* `globalReturn` - allow `return` statements in the global scope
```json
{
"parserOptions": {
- "ecmaVersion": 6,
+ "ecmaVersion": "latest",
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
/* eslint eqeqeq: "off", curly: "error" */
```
-In this example, [`eqeqeq`](https://eslint.org/docs/rules/eqeqeq) is turned off and [`curly`](.https://eslint.org/docs/rules/curly) is turned on as an error. You can also use the numeric equivalent for the rule severity:
+In this example, [`eqeqeq`](https://eslint.org/docs/rules/eqeqeq) is turned off and [`curly`](https://eslint.org/docs/rules/curly) is turned on as an error. You can also use the numeric equivalent for the rule severity:
```js
/* eslint eqeqeq: 0, curly: 2 */
## Installation and Usage
-Prerequisites: [Node.js](https://nodejs.org/en/) (`^10.12.0`, or `>=12.0.0`) built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.)
+Prerequisites: [Node.js](https://nodejs.org/en/) (`^12.22.0`, `^14.17.0`, or `>=16.0.0`) built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.)
You can install ESLint using npm or yarn:
* Broccoli: [broccoli-eslint](https://www.npmjs.com/package/broccoli-eslint)
* Browserify: [eslintify](https://www.npmjs.com/package/eslintify)
* Webpack: [eslint-webpack-plugin](https://www.npmjs.com/package/eslint-webpack-plugin)
-* Rollup: [rollup-plugin-eslint](https://www.npmjs.com/package/rollup-plugin-eslint)
+* Rollup: [@rollup/plugin-eslint](https://www.npmjs.com/package/@rollup/plugin-eslint)
* Ember-cli: [ember-cli-eslint](https://www.npmjs.com/package/ember-cli-eslint)
* Sails.js: [sails-hook-lint](https://www.npmjs.com/package/sails-hook-lint), [sails-eslint](https://www.npmjs.com/package/sails-eslint)
* Start: [@start/plugin-lib-eslint](https://www.npmjs.com/package/@start/plugin-lib-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://nvuillam.github.io/mega-linter): Linters aggregator for CI, [embedding eslint](https://nvuillam.github.io/mega-linter/descriptors/javascript_eslint/)
## Testing
--- /dev/null
+# Migrating to v8.0.0
+
+ESLint v8.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.
+
+The lists below are ordered roughly by the number of users each change is expected to affect, where the first items are expected to affect the most users.
+
+## Table of Contents
+
+### 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)
+
+### 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)
+
+### 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)
+
+---
+
+## <a name="drop-old-node"></a> Node.js 10, 13, and 15 are no longer supported
+
+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
+
+**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.
+
+**Related issue(s):** [#14023](https://github.com/eslint/eslint/issues/14023)
+
+## <a name="removed-formatters"></a> Removed `codeframe` and `table` formatters
+
+ESLint v8.0.0 has removed the `codeframe` and `table` formatters from the core. These formatters required dependencies that weren't used anywhere else in ESLint, and removing them allows us to reduce the size of ESLint, allowing for faster installation.
+
+**To address:** If you are using the `codeframe` or `table` formatters, you'll need to install the standalone [`eslint-formatter-codeframe`](https://github.com/fregante/eslint-formatter-codeframe) or [`eslint-formatter-table`](https://github.com/fregante/eslint-formatter-table) packages, respectively, to be able to use them in ESLint v8.0.0.
+
+**Related issue(s):** [#14277](https://github.com/eslint/eslint/issues/14277), [#14316](https://github.com/eslint/eslint/pull/14316)
+
+
+## <a name="comma-dangle"></a> `comma-dangle` rule schema is stricter
+
+In ESLint v7.0.0, the `comma-dangle` rule could be configured like this without error:
+
+```json
+{
+ "rules": {
+ "comma-dangle": ["error", "never", { "arrays": "always" }]
+ }
+}
+```
+
+With this configuration, the rule would ignore the third element in the array because only the second element is read. In ESLint v8.0.0, this configuration will cause ESLint to throw an error.
+
+**To address:** Change your rule configuration so that there are only two elements in the array, and the second element is either a string or an object, such as:
+
+```jsonc
+{
+ "comma-dangle": ["error", "never"],
+ // or
+ "comma-dangle": ["error", {
+ "arrays": "never",
+ "objects": "never",
+ "imports": "never",
+ "exports": "never",
+ "functions": "never"
+ }]
+}
+```
+
+**Related issue(s):** [#13739](https://github.com/eslint/eslint/issues/13739)
+
+## <a name="directives"></a> Unused disable directives are now fixable
+
+In ESLint v7.0.0, using both `--report-unused-disable-directives` and `--fix` on the command line would fix only rules but leave unused disable directives in place. In ESLint v8.0.0, this combination of command-line options will result in the unused disable directives being removed.
+
+**To address:** If you are using `--report-unused-disable-directives` and `--fix` together on the command line, and you don't want unused disable directives to be removed, add `--fix-type problem,suggestion,layout` as a command line option.
+
+**Related issue(s):** [#11815](https://github.com/eslint/eslint/issues/11815)
+
+## <a name="eslint-recommended"></a> `eslint:recommended` has been updated
+
+Four new rules have been enabled in the `eslint:recommended` preset.
+
+- [`no-loss-of-precision`](https://eslint.org/docs/rules/no-loss-of-precision)
+- [`no-nonoctal-decimal-escape`](https://eslint.org/docs/rules/no-nonoctal-decimal-escape)
+- [`no-unsafe-optional-chaining`](https://eslint.org/docs/rules/no-unsafe-optional-chaining)
+- [`no-useless-backreference`](https://eslint.org/docs/rules/no-useless-backreference)
+
+**To address:** Fix errors or disable these rules.
+
+**Related issue(s):** [#14673](https://github.com/eslint/eslint/issues/14673)
+
+
+## <a name="suggestions"></a> Rules require `meta.hasSuggestions` to provide suggestions
+
+In ESLint v7.0.0, rules that [provided suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) did not need to let ESLint know. In v8.0.0, rules providing suggestions need to set their `meta.hasSuggestions` to `true`. This informs ESLint that the rule intends to provide suggestions. Without this property, any attempt to provide a suggestion will result in an error.
+
+**To address:** If your rule provides suggestions, add `meta.hasSuggestions` to the object, such as:
+
+```js
+module.exports = {
+ meta: {
+ hasSuggestions: true
+ },
+ create(context) {
+ // your rule
+ }
+};
+```
+
+The [eslint-plugin/require-meta-has-suggestions](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/require-meta-has-suggestions.md) rule can automatically fix and enforce that your rules are properly specifying `meta.hasSuggestions`.
+
+**Related issue(s):** [#14312](https://github.com/eslint/eslint/issues/14312)
+
+## <a name="fixes"></a> Rules require `meta.fixable` to provide fixes
+
+In ESLint v7.0.0, rules that were written as a function (rather than object) were able to provide fixes. In ESLint v8.0.0, only rules written as an object are allowed to provide fixes and must have a `meta.fixable` property set to either `"code"` or `"whitespace"`.
+
+**To address:** If your rule makes fixes and is written as a function, such as:
+
+```js
+module.exports = function(context) {
+ // your rule
+};
+```
+
+Then rewrite your rule in this format:
+
+```js
+module.exports = {
+ meta: {
+ fixable: "code" // or "whitespace"
+ },
+ create(context) {
+ // your rule
+ }
+};
+```
+
+The [eslint-plugin/require-meta-fixable](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/require-meta-fixable.md) rule can automatically fix and enforce that your rules are properly specifying `meta.fixable`.
+
+The [eslint-plugin/prefer-object-rule](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-object-rule.md) rule can automatically fix and enforce that your rules are written with the object format instead of the deprecated function format.
+
+See the [rule documentation](https://eslint.org/docs/developer-guide/working-with-rules) for more information on writing rules.
+
+**Related issue(s):** [#13349](https://github.com/eslint/eslint/issues/13349)
+
+## <a name="get-comments"></a> `SourceCode#getComments()` fails in `RuleTester`
+
+Back in ESLint v4.0.0, we deprecated `SourceCode#getComments()`, but we neglected to remove it. Rather than removing it completely in v8.0.0, we are taking the intermediate step of updating `RuleTester` to fail when `SourceCode#getComments()` is used inside of a rule. As such, all existing rules will continue to work, but when the developer runs tests for the rule there will be a failure.
+
+The `SourceCode#getComments()` method will be removed in v9.0.0.
+
+**To address:** If your rule uses `SourceCode#getComments()`, please use [`SourceCode#getCommentsBefore()`, `SourceCode#getCommentsAfter()`, or `SourceCode#getCommentsInside()`](https://eslint.org/docs/developer-guide/working-with-rules#sourcecodegetcommentsbefore-sourcecodegetcommentsafter-and-sourcecodegetcommentsinside).
+
+**Related issue(s):** [#14744](https://github.com/eslint/eslint/issues/14744)
+
+## <a name="ast-format"></a> Changes to shorthand property AST format
+
+ESLint v8.0.0 includes an upgrade to Espree v8.0.0 to support new syntax. This Espree upgrade, in turn, contains an upgrade to Acorn v8.0.0, which changed how shorthand properties were represented in the AST. Here's an example:
+
+```js
+const version = 8;
+const x = {
+ version
+};
+```
+
+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"
+ }
+}
+```
+
+Note that both the `key` and the `value` properties contain the same information. Prior to Acorn v8.0.0 (and therefore prior to ESLint v8.0.0), these two nodes were represented by the same object, so you could use `===` to determine if they represented the same node, such as:
+
+```js
+// true in ESLint v7.x, false in ESLint v8.0.0
+if (propertyNode.key === propertyNode.value) {
+ // do something
+}
+```
+
+In ESLint v8.0.0 (via Acorn v8.0.0), the key and value are now separate objects and therefore no longer equivalent.
+
+**To address:** If your rule makes a comparison between the key and value of a shorthand object literal property to determine if they are the same node, you'll need to change your code in one of two ways:
+
+1. Use `propertyNode.shorthand` to determine if the property is a shorthand property node.
+1. Use the `range` property of each node to determine if the key and value occupy the same location.
+
+**Related issue(s):** [#14591](https://github.com/eslint/eslint/pull/14591#issuecomment-887733070)
+
+
+## <a name="remove-cliengine"></a> The `CLIEngine` class has been removed
+
+The `CLIEngine` class has been removed and replaced by the [`ESLint` class](https://eslint.org/docs/developer-guide/nodejs-api#eslint-class).
+
+**To address:** Update your code to use the new `ESLint` class if you are currently using `CLIEngine`. The following table maps the existing `CLIEngine` methods to their `ESLint` counterparts:
+
+| `CLIEngine` | `ESLint` |
+| :------------------------------------------- | :--------------------------------- |
+| `executeOnFiles(patterns)` | `lintFiles(patterns)` |
+| `executeOnText(text, filePath, warnIgnored)` | `lintText(text, options)` |
+| `getFormatter(name)` | `loadFormatter(name)` |
+| `getConfigForFile(filePath)` | `calculateConfigForFile(filePath)` |
+| `isPathIgnored(filePath)` | `isPathIgnored(filePath)` |
+| `static outputFixes(results)` | `static outputFixes(results)` |
+| `static getErrorResults(results)` | `static getErrorResults(results)` |
+| `static getFormatter(name)` | (removed ※1) |
+| `addPlugin(pluginId, definition)` | the `plugins` constructor option |
+| `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.
+
+**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)
+
+## <a name="remove-linter"></a> The `linter` object has been removed
+
+The deprecated `linter` object has been removed from the ESLint package in v8.0.0.
+
+**To address:** If you are using the `linter` object, such as:
+
+```js
+const { linter } = require("eslint");
+```
+
+Change your code to this:
+
+```js
+const { Linter } = require("eslint");
+const linter = new Linter();
+```
+
+**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)
+
+## <a name="remove-lib"></a> The `/lib` entrypoint has been removed
+
+Beginning in v8.0.0, ESLint is strictly defining its public API. Previously, you could reach into individual files such as `require("eslint/lib/rules/semi")` and this is no longer allowed. There are a limited number of existing APIs that are now available through the `/use-at-your-own-risk` entrypoint for backwards compatibility, but these APIs are not formally supported and may break or disappear at any point in time.
+
+**To address:** If you are accessing rules directly through the `/lib` entrypoint, such as:
+
+```js
+const rule = require("eslint/lib/rules/semi");
+```
+
+Change your code to this:
+
+```js
+const { builtinRules } = require("eslint/use-at-your-own-risk");
+const rule = builtinRules.get("semi");
+```
+
+If you are accessing `FileEnumerator` directly through the `/lib` entrypoint, such as:
+
+```js
+const { FileEnumerator } = require("eslint/lib/cli-engine/file-enumerator");
+```
+
+Change your code to this:
+
+```js
+const { FileEnumerator } = require("eslint/use-at-your-own-risk");
+```
+
+**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)
"use strict";
-const { CLIEngine } = require("./cli-engine");
+//-----------------------------------------------------------------------------
+// Requirements
+//-----------------------------------------------------------------------------
+
const { ESLint } = require("./eslint");
const { Linter } = require("./linter");
const { RuleTester } = require("./rule-tester");
const { SourceCode } = require("./source-code");
+//-----------------------------------------------------------------------------
+// Exports
+//-----------------------------------------------------------------------------
+
module.exports = {
Linter,
- CLIEngine,
ESLint,
RuleTester,
SourceCode
};
-
-// DOTO: remove deprecated API.
-let deprecatedLinterInstance = null;
-
-Object.defineProperty(module.exports, "linter", {
- enumerable: false,
- get() {
- if (!deprecatedLinterInstance) {
- deprecatedLinterInstance = new Linter();
- }
-
- return deprecatedLinterInstance;
- }
-});
const LintResultCache = require("./lint-result-cache");
const debug = require("debug")("eslint:cli-engine");
-const validFixTypes = new Set(["problem", "suggestion", "layout"]);
+const validFixTypes = new Set(["directive", "problem", "suggestion", "layout"]);
//------------------------------------------------------------------------------
// Typedefs
/** @typedef {import("../shared/types").Plugin} Plugin */
/** @typedef {import("../shared/types").RuleConf} RuleConf */
/** @typedef {import("../shared/types").Rule} Rule */
-/** @typedef {ReturnType<CascadingConfigArrayFactory["getConfigArrayForFile"]>} ConfigArray */
-/** @typedef {ReturnType<ConfigArray["extractConfig"]>} ExtractedConfig */
+/** @typedef {ReturnType<CascadingConfigArrayFactory.getConfigArrayForFile>} ConfigArray */
+/** @typedef {ReturnType<ConfigArray.extractConfig>} ExtractedConfig */
/**
* The options to configure a CLI engine with.
return messages.reduce((stat, message) => {
if (message.fatal || message.severity === 2) {
stat.errorCount++;
+ if (message.fatal) {
+ stat.fatalErrorCount++;
+ }
if (message.fix) {
stat.fixableErrorCount++;
}
return stat;
}, {
errorCount: 0,
+ fatalErrorCount: 0,
warningCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0
function calculateStatsPerRun(results) {
return results.reduce((stat, result) => {
stat.errorCount += result.errorCount;
+ stat.fatalErrorCount += result.fatalErrorCount;
stat.warningCount += result.warningCount;
stat.fixableErrorCount += result.fixableErrorCount;
stat.fixableWarningCount += result.fixableWarningCount;
return stat;
}, {
errorCount: 0,
+ fatalErrorCount: 0,
warningCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0
/**
* Returns result with warning by ignore settings
* @param {string} filePath File path of checked code
- * @param {string} baseDir Absolute path of base directory
+ * @param {string} baseDir Absolute path of base directory
* @returns {LintResult} Result with single warning
* @private
*/
return builtInRules.get(ruleId) || null;
}
+/**
+ * Checks whether a message's rule type should be fixed.
+ * @param {LintMessage} message The message to check.
+ * @param {ConfigArray[]} lastConfigArrays The list of config arrays that the last `executeOnFiles` or `executeOnText` used.
+ * @param {string[]} fixTypes An array of fix types to check.
+ * @returns {boolean} Whether the message should be fixed.
+ */
+function shouldMessageBeFixed(message, lastConfigArrays, fixTypes) {
+ if (!message.ruleId) {
+ return fixTypes.has("directive");
+ }
+
+ const rule = message.ruleId && getRule(message.ruleId, lastConfigArrays);
+
+ return Boolean(rule && rule.meta && fixTypes.has(rule.meta.type));
+}
+
/**
* Collect used deprecated rules.
* @param {ConfigArray[]} usedConfigArrays The config arrays which were used.
* @param {string[]|null} keys The keys to assign true.
* @param {boolean} defaultValue The default value for each property.
* @param {string} displayName The property name which is used in error message.
+ * @throws {Error} Requires array.
* @returns {Record<string,boolean>} The boolean map.
*/
function toBooleanMap(keys, defaultValue, displayName) {
/**
* Checks whether a directory exists at the given location
* @param {string} resolvedPath A path from the CWD
+ * @throws {Error} As thrown by `fs.statSync` or `fs.isDirectory`.
* @returns {boolean} `true` if a directory exists
*/
function directoryExists(resolvedPath) {
// Public Interface
//------------------------------------------------------------------------------
+/**
+ * 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<string,Plugin>|null} [additionalData.preloadedPlugins] Preloaded plugins.
*/
- constructor(providedOptions) {
+ constructor(providedOptions, { preloadedPlugins } = {}) {
const options = Object.assign(
Object.create(null),
defaultOptions,
}
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 originalFix = (typeof options.fix === "function")
? options.fix : () => true;
- options.fix = message => {
- const rule = message.ruleId && getRule(message.ruleId, lastConfigArrays);
- const matches = rule && rule.meta && fixTypes.has(rule.meta.type);
-
- return matches && originalFix(message);
- };
+ options.fix = message => shouldMessageBeFixed(message, lastConfigArrays, fixTypes) && originalFix(message);
}
}
});
}
-
- /**
- * Add a plugin by passing its configuration
- * @param {string} name Name of the plugin.
- * @param {Plugin} pluginObject Plugin configuration object.
- * @returns {void}
- */
- addPlugin(name, pluginObject) {
- const {
- additionalPluginPool,
- configArrayFactory,
- lastConfigArrays
- } = internalSlotsMap.get(this);
-
- additionalPluginPool.set(name, pluginObject);
- configArrayFactory.clearCache();
- lastConfigArrays.length = 1;
- lastConfigArrays[0] = configArrayFactory.getConfigArrayForFile();
- }
-
/**
* Resolves the patterns passed into executeOnFiles() into glob-based patterns
* for easier handling.
/**
* 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) {
* 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) {
* 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 {(Function|null)} The formatter function or null if the `format` is not a string.
*/
getFormatter(format) {
try {
return require(formatterPath);
} catch (ex) {
- ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`;
+ if (format === "table" || format === "codeframe") {
+ 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;
}
const IGNORED = 2;
// For VSCode intellisense
-/** @typedef {ReturnType<CascadingConfigArrayFactory["getConfigArrayForFile"]>} ConfigArray */
+/** @typedef {ReturnType<CascadingConfigArrayFactory.getConfigArrayForFile>} ConfigArray */
/**
* @typedef {Object} FileEnumeratorOptions
/**
* Get stats of a given path.
* @param {string} filePath The path to target file.
+ * @throws {Error} As may be thrown by `fs.statSync`.
* @returns {fs.Stats|null} The stats.
* @private
*/
/**
* Get filenames in a given path to a directory.
* @param {string} directoryPath The path to target directory.
+ * @throws {Error} As may be thrown by `fs.readdirSync`.
* @returns {import("fs").Dirent[]} The filenames.
* @private
*/
*/
class NoFilesFoundError extends Error {
- // eslint-disable-next-line jsdoc/require-description
/**
* @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.
*/
class AllFilesIgnoredError extends Error {
- // eslint-disable-next-line jsdoc/require-description
/**
* @param {string} pattern The glob pattern which was not found.
*/
/**
* 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<FileAndConfig>} The found files.
*/
*iterateFiles(patternOrPatterns) {
+++ /dev/null
-/**
- * @fileoverview Codeframe reporter
- * @author Vitor Balocco
- */
-"use strict";
-
-const chalk = require("chalk");
-const { codeFrameColumns } = require("@babel/code-frame");
-const path = require("path");
-
-//------------------------------------------------------------------------------
-// Helpers
-//------------------------------------------------------------------------------
-
-/**
- * Given a word and a count, append an s if count is not one.
- * @param {string} word A word in its singular form.
- * @param {number} count A number controlling whether word should be pluralized.
- * @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`);
-}
-
-/**
- * Gets a formatted relative file path from an absolute path and a line/column in the file.
- * @param {string} filePath The absolute file path to format.
- * @param {number} line The line from the file to use for formatting.
- * @param {number} column The column from the file to use for formatting.
- * @returns {string} The formatted file path.
- */
-function formatFilePath(filePath, line, column) {
- let relPath = path.relative(process.cwd(), filePath);
-
- if (line && column) {
- relPath += `:${line}:${column}`;
- }
-
- return chalk.green(relPath);
-}
-
-/**
- * Gets the formatted output for a given message.
- * @param {Object} message The object that represents this message.
- * @param {Object} parentResult The result object that this message belongs to.
- * @returns {string} The formatted output.
- */
-function formatMessage(message, parentResult) {
- const type = (message.fatal || message.severity === 2) ? chalk.red("error") : chalk.yellow("warning");
- const msg = `${chalk.bold(message.message.replace(/([^ ])\.$/u, "$1"))}`;
- const ruleId = message.fatal ? "" : chalk.dim(`(${message.ruleId})`);
- const filePath = formatFilePath(parentResult.filePath, message.line, message.column);
- const sourceCode = parentResult.output ? parentResult.output : parentResult.source;
-
- const firstLine = [
- `${type}:`,
- `${msg}`,
- ruleId ? `${ruleId}` : "",
- sourceCode ? `at ${filePath}:` : `at ${filePath}`
- ].filter(String).join(" ");
-
- const result = [firstLine];
-
- if (sourceCode) {
- result.push(
- codeFrameColumns(sourceCode, { start: { line: message.line, column: message.column } }, { highlightCode: false })
- );
- }
-
- return result.join("\n");
-}
-
-/**
- * Gets the formatted output summary for a given number of errors and warnings.
- * @param {number} errors The number of errors.
- * @param {number} warnings The number of warnings.
- * @param {number} fixableErrors The number of fixable errors.
- * @param {number} fixableWarnings The number of fixable warnings.
- * @returns {string} The formatted output summary.
- */
-function formatSummary(errors, warnings, fixableErrors, fixableWarnings) {
- const summaryColor = errors > 0 ? "red" : "yellow";
- const summary = [];
- const fixablesSummary = [];
-
- if (errors > 0) {
- summary.push(`${errors} ${pluralize("error", errors)}`);
- }
-
- if (warnings > 0) {
- summary.push(`${warnings} ${pluralize("warning", warnings)}`);
- }
-
- if (fixableErrors > 0) {
- fixablesSummary.push(`${fixableErrors} ${pluralize("error", fixableErrors)}`);
- }
-
- if (fixableWarnings > 0) {
- fixablesSummary.push(`${fixableWarnings} ${pluralize("warning", fixableWarnings)}`);
- }
-
- let output = chalk[summaryColor].bold(`${summary.join(" and ")} found.`);
-
- if (fixableErrors || fixableWarnings) {
- output += chalk[summaryColor].bold(`\n${fixablesSummary.join(" and ")} potentially fixable with the \`--fix\` option.`);
- }
-
- return output;
-}
-
-//------------------------------------------------------------------------------
-// Public Interface
-//------------------------------------------------------------------------------
-
-module.exports = function(results) {
- let errors = 0;
- let warnings = 0;
- let fixableErrors = 0;
- let fixableWarnings = 0;
-
- const resultsWithMessages = results.filter(result => result.messages.length > 0);
-
- let output = resultsWithMessages.reduce((resultsOutput, result) => {
- const messages = result.messages.map(message => `${formatMessage(message, result)}\n\n`);
-
- errors += result.errorCount;
- warnings += result.warningCount;
- fixableErrors += result.fixableErrorCount;
- fixableWarnings += result.fixableWarningCount;
-
- return resultsOutput.concat(messages);
- }, []).join("\n");
-
- output += "\n";
- output += formatSummary(errors, warnings, fixableErrors, fixableWarnings);
-
- return (errors + warnings) > 0 ? output : "";
-};
`.trimLeft();
}
-// eslint-disable-next-line jsdoc/require-description
/**
+ * Render the results.
* @param {Array} results Test results.
* @param {Object} rulesMeta Dictionary containing metadata for each rule executed by the analysis.
* @returns {string} HTML string describing the results.
+++ /dev/null
-/**
- * @fileoverview "table reporter.
- * @author Gajus Kuizinas <gajus@gajus.com>
- */
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const chalk = require("chalk"),
- table = require("table").table;
-
-//------------------------------------------------------------------------------
-// Helpers
-//------------------------------------------------------------------------------
-
-/**
- * Given a word and a count, append an "s" if count is not one.
- * @param {string} word A word.
- * @param {number} count Quantity.
- * @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`);
-}
-
-/**
- * Draws text table.
- * @param {Array<Object>} messages Error messages relating to a specific file.
- * @returns {string} A text table.
- */
-function drawTable(messages) {
- const rows = [];
-
- if (messages.length === 0) {
- return "";
- }
-
- rows.push([
- chalk.bold("Line"),
- chalk.bold("Column"),
- chalk.bold("Type"),
- chalk.bold("Message"),
- chalk.bold("Rule ID")
- ]);
-
- messages.forEach(message => {
- let messageType;
-
- if (message.fatal || message.severity === 2) {
- messageType = chalk.red("error");
- } else {
- messageType = chalk.yellow("warning");
- }
-
- rows.push([
- message.line || 0,
- message.column || 0,
- messageType,
- message.message,
- message.ruleId || ""
- ]);
- });
-
- return table(rows, {
- columns: {
- 0: {
- width: 8,
- wrapWord: true
- },
- 1: {
- width: 8,
- wrapWord: true
- },
- 2: {
- width: 8,
- wrapWord: true
- },
- 3: {
- paddingRight: 5,
- width: 50,
- wrapWord: true
- },
- 4: {
- width: 20,
- wrapWord: true
- }
- },
- drawHorizontalLine(index) {
- return index === 1;
- }
- });
-}
-
-/**
- * Draws a report (multiple tables).
- * @param {Array} results Report results for every file.
- * @returns {string} A column of text tables.
- */
-function drawReport(results) {
- let files;
-
- files = results.map(result => {
- if (!result.messages.length) {
- return "";
- }
-
- return `\n${result.filePath}\n\n${drawTable(result.messages)}`;
- });
-
- files = files.filter(content => content.trim());
-
- return files.join("");
-}
-
-//------------------------------------------------------------------------------
-// Public Interface
-//------------------------------------------------------------------------------
-
-module.exports = function(report) {
- let result,
- errorCount,
- warningCount;
-
- result = "";
- errorCount = 0;
- warningCount = 0;
-
- report.forEach(fileReport => {
- errorCount += fileReport.errorCount;
- warningCount += fileReport.warningCount;
- });
-
- if (errorCount || warningCount) {
- result = drawReport(report);
- }
-
- result += `\n${table([
- [
- chalk.red(pluralize(`${errorCount} Error`, errorCount))
- ],
- [
- chalk.yellow(pluralize(`${warningCount} Warning`, warningCount))
- ]
- ], {
- columns: {
- 0: {
- width: 110,
- wrapWord: true
- }
- },
- drawHorizontalLine() {
- return true;
- }
- })}`;
-
- return result;
-};
const prefix = " ";
let output = `${prefix}---\n`;
- output += prefix + yaml.safeDump(diagnostic).split("\n").join(`\n${prefix}`);
+ output += prefix + yaml.dump(diagnostic).split("\n").join(`\n${prefix}`);
output += "...\n";
return output;
}
/**
* hash the given string
- * @param {string} str the string to hash
- * @returns {string} the hash
+ * @param {string} str the string to hash
+ * @returns {string} the hash
*/
function hash(str) {
return murmur(str).result().toString(36);
* @private
*/
module.exports = function(s) {
- return (`${s}`).replace(/[<>&"'\x00-\x1F\x7F\u0080-\uFFFF]/gu, c => { // eslint-disable-line no-control-regex
+ return (`${s}`).replace(/[<>&"'\x00-\x1F\x7F\u0080-\uFFFF]/gu, c => { // eslint-disable-line no-control-regex -- Converting controls to entities
switch (c) {
case "<":
return "<";
*/
function countErrors(results) {
let errorCount = 0;
+ let fatalErrorCount = 0;
let warningCount = 0;
for (const result of results) {
errorCount += result.errorCount;
+ fatalErrorCount += result.fatalErrorCount;
warningCount += result.warningCount;
}
- return { errorCount, warningCount };
+ return { errorCount, fatalErrorCount, warningCount };
}
/**
if (await printResults(engine, resultsToPrint, options.format, options.outputFile)) {
// Errors and warnings from the original unfiltered results should determine the exit code
- const { errorCount, warningCount } = countErrors(results);
+ const { errorCount, fatalErrorCount, warningCount } = countErrors(results);
+
const tooManyWarnings =
options.maxWarnings >= 0 && warningCount > options.maxWarnings;
+ const shouldExitForFatalErrors =
+ options.exitOnFatalError && fatalErrorCount > 0;
if (!errorCount && tooManyWarnings) {
log.error(
);
}
+ if (shouldExitForFatalErrors) {
+ return 2;
+ }
+
return (errorCount || tooManyWarnings) ? 1 : 0;
}
--- /dev/null
+/**
+ * @fileoverview Default configuration
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//-----------------------------------------------------------------------------
+// Requirements
+//-----------------------------------------------------------------------------
+
+const Rules = require("../rules");
+
+//-----------------------------------------------------------------------------
+// Helpers
+//-----------------------------------------------------------------------------
+
+
+exports.defaultConfig = [
+ {
+ plugins: {
+ "@": {
+ parsers: {
+ espree: require("espree")
+ },
+
+ /*
+ * 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);
+ },
+
+ has(target, property) {
+ return Rules.has(property);
+ }
+ })
+ }
+ },
+ ignores: [
+ "**/node_modules/**",
+ ".git/**"
+ ],
+ languageOptions: {
+ parser: "@/espree"
+ }
+ }
+];
--- /dev/null
+/**
+ * @fileoverview Flat Config Array
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//-----------------------------------------------------------------------------
+// Requirements
+//-----------------------------------------------------------------------------
+
+const { ConfigArray, ConfigArraySymbol } = require("@humanwhocodes/config-array");
+const { flatConfigSchema } = require("./flat-config-schema");
+const { RuleValidator } = require("./rule-validator");
+const { defaultConfig } = require("./default-config");
+const recommendedConfig = require("../../conf/eslint-recommended");
+const allConfig = require("../../conf/eslint-all");
+
+//-----------------------------------------------------------------------------
+// Helpers
+//-----------------------------------------------------------------------------
+
+const ruleValidator = new RuleValidator();
+
+/**
+ * Splits a plugin identifier in the form a/b/c into two parts: a/b and c.
+ * @param {string} identifier The identifier to parse.
+ * @returns {{objectName: string, pluginName: string}} The parts of the plugin
+ * name.
+ */
+function splitPluginIdentifier(identifier) {
+ const parts = identifier.split("/");
+
+ return {
+ objectName: parts.pop(),
+ pluginName: parts.join("/")
+ };
+}
+
+//-----------------------------------------------------------------------------
+// Exports
+//-----------------------------------------------------------------------------
+
+/**
+ * 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, baseConfig: FlatConfig}} options The options
+ * to use for the config array instance.
+ */
+ constructor(configs, { basePath, baseConfig = defaultConfig }) {
+ super(configs, {
+ basePath,
+ schema: flatConfigSchema
+ });
+
+ this.unshift(baseConfig);
+ }
+
+ /* 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 (config === "eslint:recommended") {
+ return recommendedConfig;
+ }
+
+ if (config === "eslint:all") {
+ return allConfig;
+ }
+
+ 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) {
+
+ const { plugins, languageOptions, processor } = config;
+
+ // Check parser value
+ if (languageOptions && languageOptions.parser && typeof languageOptions.parser === "string") {
+ const { pluginName, objectName: parserName } = splitPluginIdentifier(languageOptions.parser);
+
+ if (!plugins || !plugins[pluginName] || !plugins[pluginName].parsers || !plugins[pluginName].parsers[parserName]) {
+ throw new TypeError(`Key "parser": Could not find "${parserName}" in plugin "${pluginName}".`);
+ }
+
+ languageOptions.parser = plugins[pluginName].parsers[parserName];
+ }
+
+ // Check processor value
+ if (processor && typeof processor === "string") {
+ const { pluginName, objectName: processorName } = splitPluginIdentifier(processor);
+
+ if (!plugins || !plugins[pluginName] || !plugins[pluginName].processors || !plugins[pluginName].processors[processorName]) {
+ throw new TypeError(`Key "processor": Could not find "${processorName}" in plugin "${pluginName}".`);
+ }
+
+ config.processor = plugins[pluginName].processors[processorName];
+ }
+
+ ruleValidator.validate(config);
+
+ return config;
+ }
+ /* eslint-enable class-methods-use-this -- Desired as instance method */
+
+}
+
+exports.FlatConfigArray = FlatConfigArray;
--- /dev/null
+/**
+ * @fileoverview Flat config schema
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//-----------------------------------------------------------------------------
+// Type Definitions
+//-----------------------------------------------------------------------------
+
+/**
+ * @typedef ObjectPropertySchema
+ * @property {Function|string} merge The function or name of the function to call
+ * to merge multiple objects with this property.
+ * @property {Function|string} validate The function or name of the function to call
+ * to validate the value of this property.
+ */
+
+//-----------------------------------------------------------------------------
+// Helpers
+//-----------------------------------------------------------------------------
+
+const ruleSeverities = new Map([
+ [0, 0], ["off", 0],
+ [1, 1], ["warn", 1],
+ [2, 2], ["error", 2]
+]);
+
+const globalVariablesValues = new Set([
+ true, "true", "writable", "writeable",
+ false, "false", "readonly", "readable", null,
+ "off"
+]);
+
+/**
+ * Check if a value is a non-null object.
+ * @param {any} value The value to check.
+ * @returns {boolean} `true` if the value is a non-null object.
+ */
+function isNonNullObject(value) {
+ return typeof value === "object" && value !== null;
+}
+
+/**
+ * Check if a value is undefined.
+ * @param {any} value The value to check.
+ * @returns {boolean} `true` if the value is undefined.
+ */
+function isUndefined(value) {
+ return typeof value === "undefined";
+}
+
+/**
+ * Deeply merges two objects.
+ * @param {Object} first The base object.
+ * @param {Object} second The overrides object.
+ * @returns {Object} An object with properties from both first and second.
+ */
+function deepMerge(first = {}, second = {}) {
+
+ /*
+ * If the second value is an array, just return it. We don't merge
+ * arrays because order matters and we can't know the correct order.
+ */
+ if (Array.isArray(second)) {
+ return second;
+ }
+
+ /*
+ * 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
+ };
+
+ for (const key of Object.keys(second)) {
+
+ // avoid hairy edge case
+ if (key === "__proto__") {
+ continue;
+ }
+
+ const firstValue = first[key];
+ const secondValue = second[key];
+
+ if (isNonNullObject(firstValue)) {
+ result[key] = deepMerge(firstValue, secondValue);
+ } else if (isUndefined(firstValue)) {
+ if (isNonNullObject(secondValue)) {
+ result[key] = deepMerge(
+ Array.isArray(secondValue) ? [] : {},
+ secondValue
+ );
+ } else if (!isUndefined(secondValue)) {
+ result[key] = secondValue;
+ }
+ }
+ }
+
+ return result;
+
+}
+
+/**
+ * Normalizes the rule options config for a given rule by ensuring that
+ * it is an array and that the first item is 0, 1, or 2.
+ * @param {Array|string|number} ruleOptions The rule options config.
+ * @returns {Array} An array of rule options.
+ */
+function normalizeRuleOptions(ruleOptions) {
+
+ const finalOptions = Array.isArray(ruleOptions)
+ ? ruleOptions.slice(0)
+ : [ruleOptions];
+
+ finalOptions[0] = ruleSeverities.get(finalOptions[0]);
+ return finalOptions;
+}
+
+//-----------------------------------------------------------------------------
+// Assertions
+//-----------------------------------------------------------------------------
+
+/**
+ * Validates that a value is a valid rule options entry.
+ * @param {any} value The value to check.
+ * @returns {void}
+ * @throws {TypeError} If the value isn't a valid rule options.
+ */
+function assertIsRuleOptions(value) {
+
+ if (typeof value !== "string" && typeof value !== "number" && !Array.isArray(value)) {
+ throw new TypeError("Expected a string, number, or array.");
+ }
+}
+
+/**
+ * Validates that a value is valid rule severity.
+ * @param {any} value The value to check.
+ * @returns {void}
+ * @throws {TypeError} If the value isn't a valid rule severity.
+ */
+function assertIsRuleSeverity(value) {
+ const severity = typeof value === "string"
+ ? ruleSeverities.get(value.toLowerCase())
+ : ruleSeverities.get(value);
+
+ if (typeof severity === "undefined") {
+ throw new TypeError("Expected severity of \"off\", 0, \"warn\", 1, \"error\", or 2.");
+ }
+}
+
+/**
+ * Validates that a given string is the form pluginName/objectName.
+ * @param {string} value The string to check.
+ * @returns {void}
+ * @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}".`);
+ }
+}
+
+/**
+ * Validates that a value is an object.
+ * @param {any} value The value to check.
+ * @returns {void}
+ * @throws {TypeError} If the value isn't an object.
+ */
+function assertIsObject(value) {
+ if (!isNonNullObject(value)) {
+ throw new TypeError("Expected an object.");
+ }
+}
+
+/**
+ * Validates that a value is an object or a string.
+ * @param {any} value The value to check.
+ * @returns {void}
+ * @throws {TypeError} If the value isn't an object or a string.
+ */
+function assertIsObjectOrString(value) {
+ if ((!value || typeof value !== "object") && typeof value !== "string") {
+ throw new TypeError("Expected an object or string.");
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Low-Level Schemas
+//-----------------------------------------------------------------------------
+
+
+/** @type {ObjectPropertySchema} */
+const numberSchema = {
+ merge: "replace",
+ validate: "number"
+};
+
+/** @type {ObjectPropertySchema} */
+const booleanSchema = {
+ merge: "replace",
+ validate: "boolean"
+};
+
+/** @type {ObjectPropertySchema} */
+const deepObjectAssignSchema = {
+ merge(first = {}, second = {}) {
+ return deepMerge(first, second);
+ },
+ validate: "object"
+};
+
+//-----------------------------------------------------------------------------
+// High-Level Schemas
+//-----------------------------------------------------------------------------
+
+/** @type {ObjectPropertySchema} */
+const globalsSchema = {
+ merge: "assign",
+ validate(value) {
+
+ assertIsObject(value);
+
+ for (const key of Object.keys(value)) {
+
+ // avoid hairy edge case
+ if (key === "__proto__") {
+ continue;
+ }
+
+ if (key !== key.trim()) {
+ throw new TypeError(`Global "${key}" has leading or trailing whitespace.`);
+ }
+
+ if (!globalVariablesValues.has(value[key])) {
+ throw new TypeError(`Key "${key}": Expected "readonly", "writable", or "off".`);
+ }
+ }
+ }
+};
+
+/** @type {ObjectPropertySchema} */
+const parserSchema = {
+ merge: "replace",
+ validate(value) {
+ assertIsObjectOrString(value);
+
+ if (typeof value === "object" && typeof value.parse !== "function" && typeof value.parseForESLint !== "function") {
+ throw new TypeError("Expected object to have a parse() or parseForESLint() method.");
+ }
+
+ if (typeof value === "string") {
+ assertIsPluginMemberName(value);
+ }
+ }
+};
+
+/** @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.");
+ }
+
+ // 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.");
+ }
+ }
+};
+
+/** @type {ObjectPropertySchema} */
+const rulesSchema = {
+ merge(first = {}, second = {}) {
+
+ const result = {
+ ...first,
+ ...second
+ };
+
+ for (const ruleId of Object.keys(result)) {
+
+ // 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.
+ */
+ }
+
+ return result;
+ },
+
+ validate(value) {
+ assertIsObject(value);
+
+ let lastRuleId;
+
+ // Performance: One try-catch has less overhead than one per loop iteration
+ try {
+
+ /*
+ * 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;
+ }
+
+ lastRuleId = ruleId;
+
+ const ruleOptions = value[ruleId];
+
+ assertIsRuleOptions(ruleOptions);
+
+ if (Array.isArray(ruleOptions)) {
+ assertIsRuleSeverity(ruleOptions[0]);
+ } else {
+ assertIsRuleSeverity(ruleOptions);
+ }
+ }
+ } catch (error) {
+ error.message = `Key "${lastRuleId}": ${error.message}`;
+ throw error;
+ }
+ }
+};
+
+/** @type {ObjectPropertySchema} */
+const sourceTypeSchema = {
+ merge: "replace",
+ validate(value) {
+ if (typeof value !== "string" || !/^(?:script|module|commonjs)$/u.test(value)) {
+ throw new TypeError("Expected \"script\", \"module\", or \"commonjs\".");
+ }
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Full schema
+//-----------------------------------------------------------------------------
+
+exports.flatConfigSchema = {
+ settings: deepObjectAssignSchema,
+ linterOptions: {
+ schema: {
+ noInlineConfig: booleanSchema,
+ reportUnusedDisableDirectives: booleanSchema
+ }
+ },
+ languageOptions: {
+ schema: {
+ ecmaVersion: numberSchema,
+ sourceType: sourceTypeSchema,
+ globals: globalsSchema,
+ parser: parserSchema,
+ parserOptions: deepObjectAssignSchema
+ }
+ },
+ processor: processorSchema,
+ plugins: pluginsSchema,
+ rules: rulesSchema
+};
--- /dev/null
+/**
+ * @fileoverview Rule Validator
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//-----------------------------------------------------------------------------
+// Requirements
+//-----------------------------------------------------------------------------
+
+const ajv = require("../shared/ajv")();
+
+//-----------------------------------------------------------------------------
+// Helpers
+//-----------------------------------------------------------------------------
+
+/**
+ * Finds a rule with the given ID in the given config.
+ * @param {string} ruleId The ID of the rule to find.
+ * @param {Object} config The config to search in.
+ * @throws {TypeError} For missing plugin or rule.
+ * @returns {{create: Function, schema: (Array|null)}} THe rule object.
+ */
+function findRuleDefinition(ruleId, config) {
+ const ruleIdParts = ruleId.split("/");
+ let pluginName, ruleName;
+
+ // built-in rule
+ if (ruleIdParts.length === 1) {
+ pluginName = "@";
+ ruleName = ruleIdParts[0];
+ } else {
+ ruleName = ruleIdParts.pop();
+ pluginName = ruleIdParts.join("/");
+ }
+
+ const errorMessageHeader = `Key "rules": Key "${ruleId}"`;
+ let errorMessage = `${errorMessageHeader}: Could not find plugin "${pluginName}".`;
+
+ // if the plugin exists then we need to check if the rule exists
+ if (config.plugins && config.plugins[pluginName]) {
+
+ const plugin = config.plugins[pluginName];
+
+ // first check for exact rule match
+ if (plugin.rules && plugin.rules[ruleName]) {
+ return config.plugins[pluginName].rules[ruleName];
+ }
+
+ errorMessage = `${errorMessageHeader}: Could not find "${ruleName}" in plugin "${pluginName}".`;
+
+ // 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
+ }
+
+ throw new TypeError(errorMessage);
+}
+
+/**
+ * Gets a complete options schema for a rule.
+ * @param {{create: Function, schema: (Array|null)}} rule A new-style rule object
+ * @returns {Object} JSON Schema for the rule's options.
+ */
+function getRuleOptionsSchema(rule) {
+
+ if (!rule) {
+ return null;
+ }
+
+ const schema = rule.schema || rule.meta && rule.meta.schema;
+
+ if (Array.isArray(schema)) {
+ if (schema.length) {
+ return {
+ type: "array",
+ items: schema,
+ minItems: 0,
+ maxItems: schema.length
+ };
+ }
+ return {
+ type: "array",
+ minItems: 0,
+ maxItems: 0
+ };
+
+ }
+
+ // Given a full schema, leave it alone
+ return schema || null;
+}
+
+//-----------------------------------------------------------------------------
+// Exports
+//-----------------------------------------------------------------------------
+
+/**
+ * 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 = findRuleDefinition(ruleId, config);
+
+ // Precompile and cache validator the first time
+ if (!this.validators.has(rule)) {
+ const schema = getRuleOptionsSchema(rule);
+
+ if (schema) {
+ this.validators.set(rule, ajv.compile(schema));
+ }
+ }
+
+ const validateRule = this.validators.get(rule);
+
+ if (validateRule) {
+
+ validateRule(ruleOptions.slice(1));
+
+ if (validateRule.errors) {
+ throw new Error(`Key "rules": Key "${ruleId}": ${
+ validateRule.errors.map(
+ error => `\tValue ${JSON.stringify(error.data)} ${error.message}.\n`
+ ).join("")
+ }`);
+ }
+ }
+ }
+ }
+}
+
+exports.RuleValidator = RuleValidator;
* @property {string} [ignorePath] The ignore file to use instead of .eslintignore.
* @property {ConfigData} [overrideConfig] Override config object, overrides all configs used with this instance
* @property {string} [overrideConfigFile] The configuration file to use.
- * @property {Record<string,Plugin>} [plugins] An array of plugin implementations.
+ * @property {Record<string,Plugin>|null} [plugins] Preloaded plugins. This is a map-like object, keys are plugin IDs and each value is implementation.
* @property {"error" | "warn" | "off"} [reportUnusedDisableDirectives] the severity to report unused eslint-disable directives.
* @property {string} [resolvePluginsRelativeTo] The folder where plugins should be resolved from, defaulting to the CWD.
* @property {string[]} [rulePaths] An array of directories to load custom rules from.
* @returns {boolean} `true` if `x` is valid fix type.
*/
function isFixType(x) {
- return x === "problem" || x === "suggestion" || x === "layout";
+ return x === "directive" || x === "problem" || x === "suggestion" || x === "layout";
}
/**
/**
* Validates and normalizes options for the wrapped CLIEngine instance.
* @param {ESLintOptions} options The options to process.
+ * @throws {ESLintInvalidOptionsError} If of any of a variety of type errors.
* @returns {ESLintOptions} The normalized options.
*/
function processOptions({
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 \"problem\", \"suggestion\", and \"layout\".");
+ 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.");
return 0;
}
+/**
+ * Main API.
+ */
class ESLint {
/**
*/
constructor(options = {}) {
const processedOptions = processOptions(options);
- const cliEngine = new CLIEngine(processedOptions);
+ const cliEngine = new CLIEngine(processedOptions, { preloadedPlugins: options.plugins });
const {
- additionalPluginPool,
configArrayFactory,
lastConfigArrays
} = getCLIEngineInternalSlots(cliEngine);
let updated = false;
- /*
- * Address `plugins` to add plugin implementations.
- * Operate the `additionalPluginPool` internal slot directly to avoid
- * using `addPlugin(id, plugin)` method that resets cache everytime.
- */
- if (options.plugins) {
- for (const [id, plugin] of Object.entries(options.plugins)) {
- additionalPluginPool.set(id, plugin);
- updated = true;
- }
- }
-
/*
* Address `overrideConfig` to set override config.
* Operate the `configArrayFactory` internal slot directly because this
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);
+ }
+ }
+
+ // 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);
+
+ }
+
/**
* Executes the current configuration on an array of file and directory names.
* @param {string[]} patterns An array of file and directory names.
...unknownOptions
} = options || {};
- for (const key of Object.keys(unknownOptions)) {
- throw new Error(`'options' must not include the unknown option '${key}'`);
+ 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");
}
const equal = require("fast-deep-equal"),
recConfig = require("../../conf/eslint-recommended"),
- ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"),
+ {
+ Legacy: {
+ ConfigOps
+ }
+ } = require("@eslint/eslintrc"),
{ Linter } = require("../linter"),
configRule = require("./config-rule");
/**
* Information about a rule configuration, in the context of a Registry.
* @typedef {Object} registryItem
- * @param {ruleConfig} config A valid configuration for the rule
- * @param {number} specificity The number of elements in the ruleConfig array
- * @param {number} errorCount The number of errors encountered when linting with the config
+ * @property {ruleConfig} config A valid configuration for the rule
+ * @property {number} specificity The number of elements in the ruleConfig array
+ * @property {number} errorCount The number of errors encountered when linting with the config
*/
/**
/**
* Create registryItems for rules
- * @param {rulesConfig} rulesConfig Hash of rule names and arrays of ruleConfig items
- * @returns {Object} registryItems for each rule in provided rulesConfig
+ * @param {rulesConfig} rulesConfig Hash of rule names and arrays of ruleConfig items
+ * @returns {Object} registryItems for each rule in provided rulesConfig
*/
function makeRegistryItems(rulesConfig) {
return Object.keys(rulesConfig).reduce((accumulator, ruleId) => {
*/
class Registry {
- // eslint-disable-next-line jsdoc/require-description
/**
* @param {rulesConfig} [rulesConfig] Hash of rule names and arrays of possible configurations
*/
* configurations.
*
* The length of the returned array will be <= MAX_CONFIG_COMBINATIONS.
- * @returns {Object[]} "rules" configurations to use for linting
+ * @returns {Object[]} "rules" configurations to use for linting
*/
buildRuleSets() {
let idx = 0;
*
* This is broken out into its own function so that it doesn't need to be
* created inside of the while loop.
- * @param {string} rule The ruleId to add.
+ * @param {string} rule The ruleId to add.
* @returns {void}
*/
const addRuleToRuleSet = function(rule) {
* Creates a registry of rules which had no error-free configs.
* The new registry is intended to be analyzed to determine whether its rules
* should be disabled or set to warning.
- * @returns {Registry} A registry of failing rules.
+ * @returns {Registry} A registry of failing rules.
*/
getFailingRulesRegistry() {
const ruleIds = Object.keys(this.rules),
/**
* Return a cloned registry containing only configs with a desired specificity
- * @param {number} specificity Only keep configs with this specificity
- * @returns {Registry} A registry of rules
+ * @param {number} specificity Only keep configs with this specificity
+ * @returns {Registry} A registry of rules
*/
filterBySpecificity(specificity) {
const ruleIds = Object.keys(this.rules),
/**
* Lint SourceCodes against all configurations in the registry, and record results
- * @param {Object[]} sourceCodes SourceCode objects for each filename
- * @param {Object} config ESLint config object
- * @param {progressCallback} [cb] Optional callback for reporting execution status
- * @returns {Registry} New registry with errorCount populated
+ * @param {Object[]} sourceCodes SourceCode objects for each filename
+ * @param {Object} config ESLint config object
+ * @param {progressCallback} [cb] Optional callback for reporting execution status
+ * @returns {Registry} New registry with errorCount populated
*/
lintSourceCode(sourceCodes, config, cb) {
let lintedRegistry = new Registry();
ruleSetIdx += 1;
if (cb) {
- cb(totalFilesLinting); // eslint-disable-line node/callback-return
+ cb(totalFilesLinting); // eslint-disable-line node/callback-return -- End of function
}
});
*
* This will return a new config with `["extends": [ ..., "eslint:recommended"]` and
* only the rules which have configurations different from the recommended config.
- * @param {Object} config config object
- * @returns {Object} config object using `"extends": ["eslint:recommended"]`
+ * @param {Object} config config object
+ * @returns {Object} config object using `"extends": ["eslint:recommended"]`
*/
function extendFromRecommended(config) {
const newConfig = Object.assign({}, config);
* Determines sort order for object keys for json-stable-stringify
*
* see: https://github.com/samn/json-stable-stringify#cmp
- * @param {Object} a The first comparison object ({key: akey, value: avalue})
- * @param {Object} b The second comparison object ({key: bkey, value: bvalue})
- * @returns {number} 1 or -1, used in stringify cmp method
+ * @param {Object} a The first comparison object ({key: akey, value: avalue})
+ * @param {Object} b The second comparison object ({key: bkey, value: bvalue})
+ * @returns {number} 1 or -1, used in stringify cmp method
*/
function sortByKey(a, b) {
return a.key > b.key ? 1 : -1;
// lazy load YAML to improve performance when not used
const yaml = require("js-yaml");
- const content = yaml.safeDump(config, { sortKeys: true });
+ const content = yaml.dump(config, { sortKeys: true });
fs.writeFileSync(filePath, content, "utf8");
}
semver = require("semver"),
espree = require("espree"),
recConfig = require("../../conf/eslint-recommended"),
- ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"),
+ {
+ Legacy: {
+ ConfigOps,
+ naming
+ }
+ } = require("@eslint/eslintrc"),
log = require("../shared/logging"),
- naming = require("@eslint/eslintrc/lib/shared/naming"),
ModuleResolver = require("../shared/relative-module-resolver"),
autoconfig = require("./autoconfig.js"),
ConfigFile = require("./config-file"),
/**
* Return necessary plugins, configs, parsers, etc. based on the config
- * @param {Object} config config object
- * @param {boolean} [installESLint=true] If `false` is given, it does not install eslint.
+ * @param {Object} config config object
+ * @param {boolean} [installESLint=true] If `false` is given, it does not install eslint.
* @returns {string[]} An array of modules to be installed.
*/
function getModulesList(config, installESLint) {
*
* Note: This clones the config object and returns a new config to avoid mutating
* the original config parameter.
- * @param {Object} answers answers received from enquirer
- * @param {Object} config config object
- * @returns {Object} config object with configured rules
+ * @param {Object} answers answers received from enquirer
+ * @param {Object} config config object
+ * @throws {Error} If source code retrieval fails or source code file count is 0.
+ * @returns {Object} config object with configured rules
*/
function configureRules(answers, config) {
const BAR_TOTAL = 20,
/**
* Install modules.
- * @param {string[]} modules Modules to be installed.
+ * @param {string[]} modules Modules to be installed.
* @returns {void}
*/
function installModules(modules) {
/* istanbul ignore next: no need to test enquirer */
/**
* Ask user to install modules.
- * @param {string[]} modules Array of modules to be installed.
- * @param {boolean} packageJsonExists Indicates if package.json is existed.
- * @returns {Promise} Answer that indicates if user wants to install.
+ * @param {string[]} modules Array of modules to be installed.
+ * @param {boolean} packageJsonExists Indicates if package.json is existed.
+ * @returns {Promise<void>} Answer that indicates if user wants to install.
*/
function askInstallModules(modules, packageJsonExists) {
/* istanbul ignore next: no need to test enquirer */
/**
* Ask use a few questions on command prompt
- * @returns {Promise} The promise with the result of the prompt
+ * @returns {Promise<void>} The promise with the result of the prompt
*/
function promptUser() {
/**
* Wrap all of the elements of an array into arrays.
- * @param {*[]} xs Any array.
- * @returns {Array[]} An array of arrays.
+ * @param {*[]} xs Any array.
+ * @returns {Array[]} An array of arrays.
*/
function explodeArray(xs) {
return xs.reduce((accumulator, x) => {
*
* For example:
* combineArrays([a, [b, c]], [x, y]); // -> [[a, x], [a, y], [b, c, x], [b, c, y]]
- * @param {Array} arr1 The first array to combine.
- * @param {Array} arr2 The second array to combine.
- * @returns {Array} A mixture of the elements of the first and second arrays.
+ * @param {Array} arr1 The first array to combine.
+ * @param {Array} arr2 The second array to combine.
+ * @returns {Array} A mixture of the elements of the first and second arrays.
*/
function combineArrays(arr1, arr2) {
const res = [];
* [{before: true}, {before: false}],
* [{after: true}, {after: false}]
* ]
- * @param {Object[]} objects Array of objects, each with one property/value pair
- * @returns {Array[]} Array of arrays of objects grouped by property
+ * @param {Object[]} objects Array of objects, each with one property/value pair
+ * @returns {Array[]} Array of arrays of objects grouped by property
*/
function groupByProperty(objects) {
const groupedObj = objects.reduce((accumulator, obj) => {
* Configs may also have one or more additional elements to specify rule
* configuration or options.
* @typedef {Array|number} ruleConfig
- * @param {number} 0 The rule's severity (0, 1, 2).
+ * @param {number} 0 The rule's severity (0, 1, 2).
*/
/**
* {before: false, after: true},
* {before: false, after: false}
* ]
- * @param {Object[]} objArr1 Single key/value objects, all with the same key
- * @param {Object[]} objArr2 Single key/value objects, all with another key
- * @returns {Object[]} Combined objects for each combination of input properties and values
+ * @param {Object[]} objArr1 Single key/value objects, all with the same key
+ * @param {Object[]} objArr2 Single key/value objects, all with another key
+ * @returns {Object[]} Combined objects for each combination of input properties and values
*/
function combinePropertyObjects(objArr1, objArr2) {
const res = [];
*/
class RuleConfigSet {
- // eslint-disable-next-line jsdoc/require-description
/**
* @param {ruleConfig[]} configs Valid rule configurations
*/
/**
* Add rule configs from an array of strings (schema enums)
- * @param {string[]} enums Array of valid rule options (e.g. ["always", "never"])
+ * @param {string[]} enums Array of valid rule options (e.g. ["always", "never"])
* @returns {void}
*/
addEnums(enums) {
/**
* Add rule configurations from a schema object
- * @param {Object} obj Schema item with type === "object"
+ * @param {Object} obj Schema item with type === "object"
* @returns {boolean} true if at least one schema for the object could be generated, false otherwise
*/
addObject(obj) {
/**
* Generate valid rule configurations based on a schema object
- * @param {Object} schema A rule's schema object
- * @returns {Array[]} Valid rule configurations
+ * @param {Object} schema A rule's schema object
+ * @returns {Array[]} Valid rule configurations
*/
function generateConfigsFromSchema(schema) {
const configSet = new RuleConfigSet();
/**
* Find the closest package.json file, starting at process.cwd (by default),
* and working up to root.
- * @param {string} [startDir=process.cwd()] Starting directory
- * @returns {string} Absolute path to closest package.json file
+ * @param {string} [startDir=process.cwd()] Starting directory
+ * @returns {string} Absolute path to closest package.json file
*/
function findPackageJson(startDir) {
let dir = path.resolve(startDir || process.cwd());
/**
* Install node modules synchronously and save to devDependencies in package.json
- * @param {string|string[]} packages Node module or modules to install
+ * @param {string|string[]} packages Node module or modules to install
* @returns {void}
*/
function installSyncSaveDev(packages) {
const packageList = Array.isArray(packages) ? packages : [packages];
- const npmProcess = spawn.sync("npm", ["i", "--save-dev"].concat(packageList),
- { stdio: "inherit" });
+ const npmProcess = spawn.sync("npm", ["i", "--save-dev"].concat(packageList), { stdio: "inherit" });
const error = npmProcess.error;
if (error && error.code === "ENOENT") {
/**
* Check whether node modules are include in a project's package.json.
- * @param {string[]} packages Array of node module names
- * @param {Object} opt Options Object
- * @param {boolean} opt.dependencies Set to true to check for direct dependencies
- * @param {boolean} opt.devDependencies Set to true to check for development dependencies
- * @param {boolean} opt.startdir Directory to begin searching from
- * @returns {Object} An object whose keys are the module names
+ * @param {string[]} packages Array of node module names
+ * @param {Object} opt Options Object
+ * @param {boolean} opt.dependencies Set to true to check for direct dependencies
+ * @param {boolean} opt.devDependencies Set to true to check for development dependencies
+ * @param {boolean} opt.startdir Directory to begin searching from
+ * @throws {Error} If cannot find valid `package.json` file.
+ * @returns {Object} An object whose keys are the module names
* and values are booleans indicating installation.
*/
function check(packages, opt) {
* package.json.
*
* Convenience wrapper around check().
- * @param {string[]} packages Array of node modules to check.
- * @param {string} rootDir The directory containing a package.json
- * @returns {Object} An object whose keys are the module names
+ * @param {string[]} packages Array of node modules to check.
+ * @param {string} rootDir The directory containing a package.json
+ * @returns {Object} An object whose keys are the module names
* and values are booleans indicating installation.
*/
function checkDeps(packages, rootDir) {
* package.json.
*
* Convenience wrapper around check().
- * @param {string[]} packages Array of node modules to check.
- * @returns {Object} An object whose keys are the module names
+ * @param {string[]} packages Array of node modules to check.
+ * @returns {Object} An object whose keys are the module names
* and values are booleans indicating installation.
*/
function checkDevDeps(packages) {
/**
* Check whether package.json is found in current path.
- * @param {string} [startDir] Starting directory
+ * @param {string} [startDir] Starting directory
* @returns {boolean} Whether a package.json is found in current path.
*/
function checkPackageJson(startDir) {
* TODO1: Expose the API that enumerates target files.
* TODO2: Extract the creation logic of `SourceCode` from `Linter` class.
*/
-const { getCLIEngineInternalSlots } = require("../cli-engine/cli-engine"); // eslint-disable-line node/no-restricted-require
+const { getCLIEngineInternalSlots } = require("../cli-engine/cli-engine"); // eslint-disable-line node/no-restricted-require -- Todo
const debug = require("debug")("eslint:source-code-utils");
/**
* Get the SourceCode object for a single file
- * @param {string} filename The fully resolved filename to get SourceCode from.
- * @param {Object} engine A CLIEngine.
- * @returns {Array} Array of the SourceCode object representing the file
+ * @param {string} filename The fully resolved filename to get SourceCode from.
+ * @param {Object} engine A CLIEngine.
+ * @throws {Error} Upon fatal errors from execution.
+ * @returns {Array} Array of the SourceCode object representing the file
* and fatal error message.
*/
function getSourceCodeOfFile(filename, engine) {
sourceCodes[filename] = sourceCode;
}
if (callback) {
- callback(filenames.length); // eslint-disable-line node/callback-return
+ callback(filenames.length); // eslint-disable-line node/callback-return -- End of function
}
});
"use strict";
+const escapeRegExp = require("escape-string-regexp");
+
/**
* Compares the locations of two objects in a source file
* @param {{line: number, column: number}} itemA The first object
return itemA.line - itemB.line || itemA.column - itemB.column;
}
+/**
+ * Groups a set of directives into sub-arrays by their parent comment.
+ * @param {Directive[]} directives Unused directives to be removed.
+ * @returns {Directive[][]} Directives grouped by their parent comment.
+ */
+function groupByParentComment(directives) {
+ const groups = new Map();
+
+ for (const directive of directives) {
+ const { unprocessedDirective: { parentComment } } = directive;
+
+ if (groups.has(parentComment)) {
+ groups.get(parentComment).push(directive);
+ } else {
+ groups.set(parentComment, [directive]);
+ }
+ }
+
+ return [...groups.values()];
+}
+
+/**
+ * Creates removal details for a set of directives within the same comment.
+ * @param {Directive[]} directives Unused directives to be removed.
+ * @param {Token} commentToken The backing Comment token.
+ * @returns {{ description, fix, position }[]} Details for later creation of output Problems.
+ */
+function createIndividualDirectivesRemoval(directives, commentToken) {
+
+ /*
+ * `commentToken.value` starts right after `//` or `/*`.
+ * All calculated offsets will be relative to this index.
+ */
+ const commentValueStart = commentToken.range[0] + "//".length;
+
+ // Find where the list of rules starts. `\S+` matches with the directive name (e.g. `eslint-disable-line`)
+ const listStartOffset = /^\s*\S+\s+/u.exec(commentToken.value)[0].length;
+
+ /*
+ * Get the list 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 = commentToken.value
+ .slice(listStartOffset) // remove directive name and all whitespace before the list
+ .split(/\s-{2,}\s/u)[0] // remove `-- comment`, if it exists
+ .trimRight(); // remove all whitespace after the list
+
+ /*
+ * 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)}(?:\s*,\s*|$)`, "u");
+ const match = regex.exec(listText);
+ const matchedText = match[0];
+ const matchStartOffset = listStartOffset + match.index;
+ const matchEndOffset = matchStartOffset + matchedText.length;
+
+ const firstIndexOfComma = matchedText.indexOf(",");
+ const lastIndexOfComma = matchedText.lastIndexOf(",");
+
+ let removalStartOffset, removalEndOffset;
+
+ 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
+ * ^^^^^^^^^^^
+ */
+ removalStartOffset = matchStartOffset + firstIndexOfComma;
+ removalEndOffset = matchStartOffset + 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
+ * ^^^^^^^^^^^^^
+ */
+ removalStartOffset = matchStartOffset;
+ removalEndOffset = matchEndOffset;
+ }
+
+ return {
+ description: `'${ruleId}'`,
+ fix: {
+ range: [
+ commentValueStart + removalStartOffset,
+ commentValueStart + removalEndOffset
+ ],
+ text: ""
+ },
+ position: directive.unprocessedDirective
+ };
+ });
+}
+
+/**
+ * Creates a description of deleting an entire unused disable comment.
+ * @param {Directive[]} directives Unused directives to be removed.
+ * @param {Token} commentToken The backing Comment token.
+ * @returns {{ description, fix, position }} Details for later creation of an output Problem.
+ */
+function createCommentRemoval(directives, commentToken) {
+ const { range } = commentToken;
+ 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[ruleIds.length - 1]}`,
+ fix: {
+ range,
+ text: " "
+ },
+ position: directives[0].unprocessedDirective
+ };
+}
+
+/**
+ * Parses details from directives to create output Problems.
+ * @param {Directive[]} allDirectives Unused directives to be removed.
+ * @returns {{ description, fix, position }[]} Details for later creation of output Problems.
+ */
+function processUnusedDisableDirectives(allDirectives) {
+ const directiveGroups = groupByParentComment(allDirectives);
+
+ return directiveGroups.flatMap(
+ directives => {
+ const { parentComment } = directives[0].unprocessedDirective;
+ const remainingRuleIds = new Set(parentComment.ruleIds);
+
+ for (const directive of directives) {
+ remainingRuleIds.delete(directive.ruleId);
+ }
+
+ return remainingRuleIds.size
+ ? createIndividualDirectivesRemoval(directives, parentComment.commentToken)
+ : [createCommentRemoval(directives, parentComment.commentToken)];
+ }
+ );
+}
+
/**
* This is the same as the exported function, except that it
* doesn't handle disable-line and disable-next-line directives, and it always reports unused
}
}
- const unusedDisableDirectives = options.directives
- .filter(directive => directive.type === "disable" && !usedDisableDirectives.has(directive))
- .map(directive => ({
+ const unusedDisableDirectivesToReport = options.directives
+ .filter(directive => directive.type === "disable" && !usedDisableDirectives.has(directive));
+
+ const processed = processUnusedDisableDirectives(unusedDisableDirectivesToReport);
+
+ const unusedDisableDirectives = processed
+ .map(({ description, fix, position }) => ({
ruleId: null,
- message: directive.ruleId
- ? `Unused eslint-disable directive (no problems were reported from '${directive.ruleId}').`
+ message: description
+ ? `Unused eslint-disable directive (no problems were reported from ${description}).`
: "Unused eslint-disable directive (no problems were reported).",
- line: directive.unprocessedDirective.line,
- column: directive.unprocessedDirective.column,
+ line: position.line,
+ column: position.column,
severity: options.reportUnusedDisableDirectives === "warn" ? 1 : 2,
- nodeType: null
+ nodeType: null,
+ ...options.disableFixes ? {} : { fix }
}));
return { problems, unusedDisableDirectives };
* @param {{ruleId: (string|null), line: number, column: number}[]} options.problems
* A list of problems reported by rules, sorted by increasing location in the file, with one-based columns.
* @param {"off" | "warn" | "error"} options.reportUnusedDisableDirectives If `"warn"` or `"error"`, adds additional problems for unused directives
+ * @param {boolean} options.disableFixes If true, it doesn't make `fix` properties.
* @returns {{ruleId: (string|null), line: number, column: number}[]}
* A list of reported problems that were not disabled by the directive comments.
*/
-module.exports = ({ directives, problems, reportUnusedDisableDirectives = "off" }) => {
+module.exports = ({ directives, disableFixes, problems, reportUnusedDisableDirectives = "off" }) => {
const blockDirectives = directives
.filter(directive => directive.type === "disable" || directive.type === "enable")
.map(directive => Object.assign({}, directive, { unprocessedDirective: directive }))
.sort(compareLocations);
- /**
- * Returns a new array formed by applying a given callback function to each element of the array, and then flattening the result by one level.
- * TODO(stephenwade): Replace this with array.flatMap when we drop support for Node v10
- * @param {any[]} array The array to process
- * @param {Function} fn The function to use
- * @returns {any[]} The result array
- */
- function flatMap(array, fn) {
- const mapped = array.map(fn);
- const flattened = [].concat(...mapped);
-
- return flattened;
- }
-
- const lineDirectives = flatMap(directives, directive => {
+ const lineDirectives = directives.flatMap(directive => {
switch (directive.type) {
case "disable":
case "enable":
const blockDirectivesResult = applyDirectives({
problems,
directives: blockDirectives,
+ disableFixes,
reportUnusedDisableDirectives
});
const lineDirectivesResult = applyDirectives({
problems: blockDirectivesResult.problems,
directives: lineDirectives,
+ disableFixes,
reportUnusedDisableDirectives
});
return Boolean(node.test);
}
+/**
+ * Checks if a given node appears as the value of a PropertyDefinition node.
+ * @param {ASTNode} node THe node to check.
+ * @returns {boolean} `true` if the node is a PropertyDefinition value,
+ * false if not.
+ */
+function isPropertyDefinitionValue(node) {
+ const parent = node.parent;
+
+ return parent && parent.type === "PropertyDefinition" && parent.value === node;
+}
+
/**
* Checks whether the given logical operator is taken into account for the code
* path analysis.
return parent.id !== node;
case "Property":
+ case "PropertyDefinition":
case "MethodDefinition":
return (
parent.key !== node ||
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":
- 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(
- analyzer.idGenerator.next(),
- codePath,
- analyzer.onLooped
- );
- state = CodePath.getState(codePath);
+ startCodePath("function");
+ break;
- // Emits onCodePathStart events.
- debug.dump(`onCodePathStart ${codePath.id}`);
- analyzer.emitter.emit("onCodePathStart", codePath, node);
+ case "StaticBlock":
+ startCodePath("class-static-block");
break;
case "ChainExpression":
* @returns {void}
*/
function processCodePathToExit(analyzer, node) {
+
const codePath = analyzer.codePath;
const state = CodePath.getState(codePath);
let dontForward = false;
* @returns {void}
*/
function postprocess(analyzer, node) {
- switch (node.type) {
- case "Program":
- case "FunctionDeclaration":
- case "FunctionExpression":
- case "ArrowFunctionExpression": {
- let codePath = analyzer.codePath;
- // Mark the current path as the final node.
- CodePath.getState(codePath).makeFinal();
+ /**
+ * 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 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);
+ // 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);
- }
+ 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;
}
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();
+ }
}
//------------------------------------------------------------------------------
*/
class CodePathAnalyzer {
- // eslint-disable-next-line jsdoc/require-description
/**
* @param {EventGenerator} eventGenerator An event generator to wrap.
*/
*/
class CodePathSegment {
- // eslint-disable-next-line jsdoc/require-description
/**
* @param {string} id An identifier.
* @param {CodePathSegment[]} allPrevSegments An array of the previous segments.
*/
class CodePathState {
- // eslint-disable-next-line jsdoc/require-description
/**
* @param {IdGenerator} idGenerator An id generator to generate id for code
* path segments.
/**
* Pops the last choice context and finalizes it.
+ * @throws {Error} (Unreachable.)
* @returns {ChoiceContext} The popped context.
*/
popChoiceContext() {
/**
* Makes a code path segment of the right-hand operand of a logical
* expression.
+ * @throws {Error} (Unreachable.)
* @returns {void}
*/
makeLogicalRight() {
* `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) {
/**
* Pops the last context of a loop statement and finalizes it.
+ * @throws {Error} (Unreachable - unknown type.)
* @returns {void}
*/
popLoopContext() {
*/
class CodePath {
- // eslint-disable-next-line jsdoc/require-description
/**
- * @param {string} id An identifier.
- * @param {CodePath|null} upper The code path of the upper function scope.
- * @param {Function} onLooped A callback function to notify looping.
+ * 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, upper, onLooped) {
+ constructor({ id, origin, upper, onLooped }) {
/**
* The identifier of this code path.
*/
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}
* @returns {string} Id of the segment.
*/
/* istanbul ignore next */
-function getId(segment) { // eslint-disable-line jsdoc/require-jsdoc
+function getId(segment) { // eslint-disable-line jsdoc/require-jsdoc -- Ignoring
return segment.id + (segment.reachable ? "" : "!");
}
const traceMap = Object.create(null);
const arrows = this.makeDotArrows(codePath, traceMap);
- for (const id in traceMap) { // eslint-disable-line guard-for-in
+ for (const id in traceMap) { // eslint-disable-line guard-for-in -- Want ability to traverse prototype
const segment = traceMap[id];
text += `${id}[`;
*/
class ForkContext {
- // eslint-disable-next-line jsdoc/require-description
/**
* @param {IdGenerator} idGenerator An identifier generator for segments.
* @param {ForkContext|null} upper An upper fork context.
*/
class IdGenerator {
- // eslint-disable-next-line jsdoc/require-description
/**
* @param {string} prefix Optional. A prefix of generated ids.
*/
* @author Nicholas C. Zakas
*/
-/* eslint-disable class-methods-use-this*/
+/* eslint class-methods-use-this: off -- Methods desired on instance */
"use strict";
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
const levn = require("levn"),
- ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops");
+ {
+ Legacy: {
+ ConfigOps
+ }
+ } = require("@eslint/eslintrc/universal");
const debug = require("debug")("eslint:config-comment-parser");
evk = require("eslint-visitor-keys"),
espree = require("espree"),
merge = require("lodash.merge"),
- BuiltInEnvironments = require("@eslint/eslintrc/conf/environments"),
pkg = require("../../package.json"),
astUtils = require("../shared/ast-utils"),
- ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"),
- ConfigValidator = require("@eslint/eslintrc/lib/shared/config-validator"),
+ {
+ Legacy: {
+ ConfigOps,
+ ConfigValidator,
+ environments: BuiltInEnvironments
+ }
+ } = require("@eslint/eslintrc/universal"),
Traverser = require("../shared/traverser"),
{ SourceCode } = require("../source-code"),
CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
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 parserSymbol = Symbol.for("eslint.RuleTester.parser");
//------------------------------------------------------------------------------
// Typedefs
//------------------------------------------------------------------------------
-/** @typedef {InstanceType<import("../cli-engine/config-array")["ConfigArray"]>} ConfigArray */
-/** @typedef {InstanceType<import("../cli-engine/config-array")["ExtractedConfig"]>} ExtractedConfig */
+/** @typedef {InstanceType<import("../cli-engine/config-array").ConfigArray>} ConfigArray */
+/** @typedef {InstanceType<import("../cli-engine/config-array").ExtractedConfig>} ExtractedConfig */
/** @typedef {import("../shared/types").ConfigData} ConfigData */
/** @typedef {import("../shared/types").Environment} Environment */
/** @typedef {import("../shared/types").GlobalConf} GlobalConf */
/** @typedef {import("../shared/types").Processor} Processor */
/** @typedef {import("../shared/types").Rule} Rule */
+/* eslint-disable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */
/**
* @template T
* @typedef {{ [P in keyof T]-?: T[P] }} Required
*/
+/* eslint-enable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */
/**
* @typedef {Object} DisableDirective
- * @property {("disable"|"enable"|"disable-line"|"disable-next-line")} type
- * @property {number} line
- * @property {number} column
- * @property {(string|null)} ruleId
+ * @property {("disable"|"enable"|"disable-line"|"disable-next-line")} type Type of directive
+ * @property {number} line The line number
+ * @property {number} column The column number
+ * @property {(string|null)} ruleId The rule ID
*/
/**
* @typedef {Object} ProcessorOptions
* @property {(filename:string, text:string) => boolean} [filterCodeBlock] the
* predicate function that selects adopt code blocks.
- * @property {Processor["postprocess"]} [postprocess] postprocessor for report
+ * @property {Processor.postprocess} [postprocess] postprocessor for report
* messages. If provided, this should accept an array of the message lists
* for each code block returned from the preprocessor, apply a mapping to
* the messages as appropriate, and return a one-dimensional array of
* messages.
- * @property {Processor["preprocess"]} [preprocess] preprocessor for source text.
+ * @property {Processor.preprocess} [preprocess] preprocessor for source text.
* If provided, this should accept a string of source text, and return an
* array of code blocks to lint.
*/
* Creates a collection of disable directives from a comment
* @param {Object} options to create disable directives
* @param {("disable"|"enable"|"disable-line"|"disable-next-line")} options.type The type of directive comment
- * @param {{line: number, column: number}} options.loc The 0-based location of the comment token
+ * @param {token} options.commentToken The Comment token
* @param {string} options.value The value after the directive in the comment
* comment specified no specific rules, so it applies to all rules (e.g. `eslint-disable`)
* @param {function(string): {create: Function}} options.ruleMapper A map from rule IDs to defined rules
* @returns {Object} Directives and problems from the comment
*/
function createDisableDirectives(options) {
- const { type, loc, value, ruleMapper } = options;
+ const { commentToken, type, value, ruleMapper } = options;
const ruleIds = Object.keys(commentParser.parseListConfig(value));
const directiveRules = ruleIds.length ? ruleIds : [null];
const result = {
directiveProblems: [] // problems in directives
};
+ const parentComment = { commentToken, ruleIds };
+
for (const ruleId of directiveRules) {
// push to directives, if the rule is defined(including null, e.g. /*eslint enable*/)
if (ruleId === null || ruleMapper(ruleId) !== null) {
- result.directives.push({ type, line: loc.start.line, column: loc.start.column + 1, ruleId });
+ result.directives.push({ parentComment, type, line: commentToken.loc.start.line, column: commentToken.loc.start.column + 1, ruleId });
} else {
- result.directiveProblems.push(createLintingProblem({ ruleId, loc }));
+ result.directiveProblems.push(createLintingProblem({ ruleId, loc: commentToken.loc }));
}
}
return result;
case "eslint-disable-next-line":
case "eslint-disable-line": {
const directiveType = directiveText.slice("eslint-".length);
- const options = { type: directiveType, loc: comment.loc, value: directiveValue, ruleMapper };
+ const options = { commentToken: comment, type: directiveType, value: directiveValue, ruleMapper };
const { directives, directiveProblems } = createDisableDirectives(options);
disableDirectives.push(...directives);
/**
* Normalize ECMAScript version from the initial config
- * @param {number} ecmaVersion ECMAScript version from the initial config
+ * @param {Parser} parser The parser which uses this options.
+ * @param {number} ecmaVersion ECMAScript version from the initial config
* @returns {number} normalized ECMAScript version
*/
-function normalizeEcmaVersion(ecmaVersion) {
+function normalizeEcmaVersion(parser, ecmaVersion) {
+ if ((parser[parserSymbol] || parser) === espree) {
+ if (ecmaVersion === "latest") {
+ return espree.latestEcmaVersion;
+ }
+ }
/*
* Calculate ECMAScript edition number from official year version starting with
return ecmaVersion >= 2015 ? ecmaVersion - 2009 : ecmaVersion;
}
-const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)\*\//gsu;
+const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)(?:\*\/|$)/gsu;
/**
* Checks whether or not there is a comment which has "eslint-env *" in a given text.
eslintEnvPattern.lastIndex = 0;
while ((match = eslintEnvPattern.exec(text)) !== null) {
- retv = Object.assign(
- retv || {},
- commentParser.parseListConfig(stripDirectiveComment(match[1]))
- );
+ if (match[0].endsWith("*/")) {
+ retv = Object.assign(
+ retv || {},
+ commentParser.parseListConfig(stripDirectiveComment(match[1]))
+ );
+ }
}
return retv;
/**
* Combines the provided parserOptions with the options from environments
- * @param {string} parserName The parser name which uses this options.
+ * @param {Parser} parser The parser which uses this options.
* @param {ParserOptions} providedOptions The provided 'parserOptions' key in a config
* @param {Environment[]} enabledEnvironments The environments enabled in configuration and with inline comments
* @returns {ParserOptions} Resulting parser options after merge
*/
-function resolveParserOptions(parserName, providedOptions, enabledEnvironments) {
+function resolveParserOptions(parser, providedOptions, enabledEnvironments) {
+
const parserOptionsFromEnv = enabledEnvironments
.filter(env => env.parserOptions)
.reduce((parserOptions, env) => merge(parserOptions, env.parserOptions), {});
mergedParserOptions.ecmaFeatures = Object.assign({}, mergedParserOptions.ecmaFeatures, { globalReturn: false });
}
- /*
- * TODO: @aladdin-add
- * 1. for a 3rd-party parser, do not normalize parserOptions
- * 2. for espree, no need to do this (espree will do it)
- */
- mergedParserOptions.ecmaVersion = normalizeEcmaVersion(mergedParserOptions.ecmaVersion);
+ mergedParserOptions.ecmaVersion = normalizeEcmaVersion(parser, mergedParserOptions.ecmaVersion);
return mergedParserOptions;
}
*/
function analyzeScope(ast, parserOptions, visitorKeys) {
const ecmaFeatures = parserOptions.ecmaFeatures || {};
- const ecmaVersion = parserOptions.ecmaVersion || 5;
+ const ecmaVersion = parserOptions.ecmaVersion || DEFAULT_ECMA_VERSION;
return eslintScope.analyze(ast, {
ignoreEval: true,
nodejsScope: ecmaFeatures.globalReturn,
impliedStrict: ecmaFeatures.impliedStrict,
- ecmaVersion,
+ ecmaVersion: typeof ecmaVersion === "number" ? ecmaVersion : 6,
sourceType: parserOptions.sourceType || "script",
childVisitorKeys: visitorKeys || evk.KEYS,
fallback: Traverser.getKeys
* Runs a rule, and gets its listeners
* @param {Rule} rule A normalized rule with a `create` method
* @param {Context} ruleContext The context that should be passed to the rule
+ * @throws {any} Any error during the rule's `create`
* @returns {Object} A map of selector listeners provided by the rule
*/
function createRuleListeners(rule, ruleContext) {
}
const problem = reportTranslator(...args);
- if (problem.fix && rule.meta && !rule.meta.fixable) {
- throw new Error("Fixable rules should export a `meta.fixable` property.");
+ 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 ruleListeners = createRuleListeners(rule, ruleContext);
+ /**
+ * 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 {
+ return ruleListener(...listenerArgs);
+ } catch (e) {
+ e.ruleId = ruleId;
+ throw e;
+ }
+ };
+ }
+
// add all the selectors from the rule as listeners
Object.keys(ruleListeners).forEach(selector => {
+ const ruleListener = timing.enabled
+ ? timing.time(ruleId, ruleListeners[selector])
+ : ruleListeners[selector];
+
emitter.on(
selector,
- timing.enabled
- ? timing.time(ruleId, ruleListeners[selector])
- : ruleListeners[selector]
+ addRuleErrorHandler(ruleListener)
);
});
});
}
// It's more explicit to assign the undefined
- // eslint-disable-next-line no-undefined
+ // eslint-disable-next-line no-undefined -- Consistently returning a value
return undefined;
}
/**
* 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 {string} [config.cwd] path to a directory that should be considered as the current working directory, can be undefined.
*/
constructor({ cwd } = {}) {
internalSlotsMap.set(this, {
* @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[]} The results as an array of messages or an empty array if no messages.
*/
_verifyWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) {
.map(envName => getEnv(slots, envName))
.filter(env => env);
- const parserOptions = resolveParserOptions(parserName, config.parserOptions || {}, enabledEnvs);
+ const parserOptions = resolveParserOptions(parser, config.parserOptions || {}, enabledEnvs);
const configuredGlobals = resolveGlobals(config.globals || {}, enabledEnvs);
const settings = config.settings || {};
debug("Parser Options:", parserOptions);
debug("Parser Path:", parserName);
debug("Settings:", settings);
+
+ if (err.ruleId) {
+ err.message += `\nRule: "${err.ruleId}"`;
+ }
+
throw err;
}
return applyDisableDirectives({
directives: commentDirectives.disableDirectives,
+ disableFixes: options.disableFixes,
problems: lintingProblems
.concat(commentDirectives.problems)
.sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column),
const text = ensureText(textOrSourceCode);
const preprocess = options.preprocess || (rawText => [rawText]);
- // TODO(stephenwade): Replace this with array.flat() when we drop support for Node v10
- const postprocess = options.postprocess || (array => [].concat(...array));
+ const postprocess = options.postprocess || (messagesList => messagesList.flat());
const filterCodeBlock =
options.filterCodeBlock ||
(blockFilename => blockFilename.endsWith(".js"));
* @returns {any[]} The union of the input arrays
*/
function union(...arrays) {
-
- // TODO(stephenwade): Replace this with arrays.flat() when we drop support for Node v10
- return [...new Set([].concat(...arrays))];
+ return [...new Set(arrays.flat())];
}
/**
case "adjacent":
return getPossibleTypes(parsedSelector.right);
+ case "class":
+ if (parsedSelector.name === "function") {
+ return ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"];
+ }
+
+ return null;
+
default:
return null;
*/
class NodeEventGenerator {
- // eslint-disable-next-line jsdoc/require-description
/**
* @param {SafeEmitter} emitter
* An SafeEmitter which is the destination of events. This emitter must already
/**
* Information about the report
* @typedef {Object} ReportInfo
- * @property {string} ruleId
- * @property {(0|1|2)} severity
- * @property {(string|undefined)} message
- * @property {(string|undefined)} [messageId]
- * @property {number} line
- * @property {number} column
- * @property {(number|undefined)} [endLine]
- * @property {(number|undefined)} [endColumn]
- * @property {(string|null)} nodeType
- * @property {string} source
- * @property {({text: string, range: (number[]|null)}|null)} [fix]
- * @property {Array<{text: string, range: (number[]|null)}|null>} [suggestions]
+ * @property {string} ruleId The rule ID
+ * @property {(0|1|2)} severity Severity of the error
+ * @property {(string|undefined)} message The message
+ * @property {(string|undefined)} [messageId] The message ID
+ * @property {number} line The line number
+ * @property {number} column The column number
+ * @property {(number|undefined)} [endLine] The ending line number
+ * @property {(number|undefined)} [endColumn] The ending column number
+ * @property {(string|null)} nodeType Type of node
+ * @property {string} source Source text
+ * @property {({text: string, range: (number[]|null)}|null)} [fix] The fix object
+ * @property {Array<{text: string, range: (number[]|null)}|null>} [suggestions] Suggestion info
*/
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
+/**
+ * A storage for rules.
+ */
class Rules {
constructor() {
this._rules = Object.create(null);
/**
* An event emitter
* @typedef {Object} SafeEmitter
- * @property {function(eventName: string, listenerFunc: Function): void} on Adds a listener for a given event name
- * @property {function(eventName: string, arg1?: any, arg2?: any, arg3?: any)} emit Emits an event with a given name.
+ * @property {(eventName: string, listenerFunc: Function) => void} on Adds a listener for a given event name
+ * @property {(eventName: string, arg1?: any, arg2?: any, arg3?: any) => void} emit Emits an event with a given name.
* This calls all the listeners that were listening for that name, with `arg1`, `arg2`, and `arg3` as arguments.
* @property {function(): string[]} eventNames Gets the list of event names that have registered listeners.
*/
/**
* 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
+ * @param {Message} problem The message object to apply fixes from
+ * @returns {boolean} Whether fix was successfully applied
*/
function attemptFix(problem) {
const fix = problem.fix;
return ALIGN[index](":", width + extraAlignment, "-");
}).join("|"));
- console.log(table.join("\n")); // eslint-disable-line no-console
+ console.log(table.join("\n")); // eslint-disable-line no-console -- Debugging function
}
/* istanbul ignore next */
/**
* Time the run
- * @param {*} key key from the data object
+ * @param {any} key key from the data object
* @param {Function} fn function to be called
* @returns {Function} function to be executed
* @private
* @property {string[]} [ext] Specify JavaScript file extensions
* @property {boolean} fix Automatically fix problems
* @property {boolean} fixDryRun Automatically fix problems without saving the changes to the file system
- * @property {("problem" | "suggestion" | "layout")[]} [fixType] Specify the types of fixes to apply (problem, suggestion, layout)
+ * @property {("directive" | "problem" | "suggestion" | "layout")[]} [fixType] Specify the types of fixes to apply (directive, problem, suggestion, layout)
* @property {string} format Use a specific output format
* @property {string[]} [global] Define global variables
* @property {boolean} [help] Show help
{
option: "fix-type",
type: "Array",
- description: "Specify the types of fixes to apply (problem, suggestion, layout)"
+ description: "Specify the types of fixes to apply (directive, problem, suggestion, layout)"
},
{
heading: "Ignoring files"
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"
+ },
{
option: "debug",
type: "Boolean",
*/
"use strict";
-/* global describe, it */
+/* eslint-env mocha -- Mocha wrapper */
/*
* This is a wrapper around mocha to allow for DRY unittests for eslint
const ajv = require("../shared/ajv")({ strictDefaults: true });
const espreePath = require.resolve("espree");
+const parserSymbol = Symbol.for("eslint.RuleTester.parser");
+
+const { SourceCode } = require("../source-code");
//------------------------------------------------------------------------------
// Typedefs
/** @typedef {import("../shared/types").Parser} Parser */
+/* eslint-disable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */
/**
* A test case that is expected to pass lint.
* @typedef {Object} ValidTestCase
+ * @property {string} [name] Name for the test case.
* @property {string} code Code for the test case.
* @property {any[]} [options] Options for the test case.
* @property {{ [name: string]: any }} [settings] Settings for the test case.
* @property {{ [name: string]: any }} [parserOptions] Options for the parser.
* @property {{ [name: string]: "readonly" | "writable" | "off" }} [globals] The additional global variables.
* @property {{ [name: string]: boolean }} [env] Environments for the test case.
+ * @property {boolean} [only] Run only this test case or the subset of test cases with this property.
*/
/**
* A test case that is expected to fail lint.
* @typedef {Object} InvalidTestCase
+ * @property {string} [name] Name for the test case.
* @property {string} code Code for the test case.
* @property {number | Array<TestCaseError | string | RegExp>} errors Expected errors.
* @property {string | null} [output] The expected code after autofixes are applied. If set to `null`, the test runner will assert that no autofix is suggested.
* @property {{ [name: string]: any }} [parserOptions] Options for the parser.
* @property {{ [name: string]: "readonly" | "writable" | "off" }} [globals] The additional global variables.
* @property {{ [name: string]: boolean }} [env] Environments for the test case.
+ * @property {boolean} [only] Run only this test case or the subset of test cases with this property.
*/
/**
* @property {number} [endLine] The 1-based line number of the reported end location.
* @property {number} [endColumn] The 1-based column number of the reported end location.
*/
+/* eslint-enable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */
//------------------------------------------------------------------------------
// Private Members
* configuration
*/
const RuleTesterParameters = [
+ "name",
"code",
"filename",
"options",
"errors",
- "output"
+ "output",
+ "only"
];
/*
*/
function sanitize(text) {
return text.replace(
- /[\u0000-\u0009\u000b-\u001a]/gu, // eslint-disable-line no-control-regex
+ /[\u0000-\u0009\u000b-\u001a]/gu, // eslint-disable-line no-control-regex -- Escaping controls
c => `\\u${c.codePointAt(0).toString(16).padStart(4, "0")}`
);
}
});
}
+
/**
* 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.
* @returns {Parser} Wrapped parser object.
*/
function wrapParser(parser) {
+
if (typeof parser.parseForESLint === "function") {
return {
+ [parserSymbol]: parser,
parseForESLint(...args) {
const ret = parser.parseForESLint(...args);
}
};
}
+
return {
+ [parserSymbol]: parser,
parse(...args) {
const ast = parser.parse(...args);
};
}
+/**
+ * Function to replace `SourceCode.prototype.getComments`.
+ * @returns {void}
+ * @throws {Error} Deprecation message.
+ */
+function getCommentsDeprecation() {
+ throw new Error(
+ "`SourceCode#getComments()` is deprecated and will be removed in a future major version. Use `getCommentsBefore()`, `getCommentsAfter()`, and `getCommentsInside()` instead."
+ );
+}
+
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
// default separators for testing
const DESCRIBE = Symbol("describe");
const IT = Symbol("it");
+const IT_ONLY = Symbol("itOnly");
/**
* This is `it` default handler if `it` don't exist.
* @this {Mocha}
* @param {string} text The description of the test case.
* @param {Function} method The logic of the test case.
+ * @throws {Error} Any error upon execution of `method`.
* @returns {any} Returned value of `method`.
*/
function itDefaultHandler(text, method) {
return method.call(this);
}
+/**
+ * Mocha test wrapper.
+ */
class RuleTester {
/**
/**
* 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) {
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/developer-guide/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;
+ }
+
/**
* Define a rule for one particular run of tests.
* @param {string} name The name of the rule to define.
* valid: (ValidTestCase | string)[],
* invalid: InvalidTestCase[]
* }} test The collection of tests to run.
+ * @throws {TypeError|Error} If non-object `test`, or if a required
+ * scenario of the given type is missing.
* @returns {void}
*/
run(ruleName, rule, test) {
/**
* 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
*/
validate(config, "rule-tester", id => (id === ruleName ? rule : null));
// Verify the code.
- const messages = linter.verify(code, config, filename);
+ const { getComments } = SourceCode.prototype;
+ let messages;
+
+ try {
+ SourceCode.prototype.getComments = getCommentsDeprecation;
+ messages = linter.verify(code, config, filename);
+ } finally {
+ SourceCode.prototype.getComments = getComments;
+ }
+
const fatalErrorMessage = messages.find(m => m.fatal);
assert(!fatalErrorMessage, `A fatal parsing error occurred: ${fatalErrorMessage && fatalErrorMessage.message}`);
const messages = result.messages;
assert.strictEqual(messages.length, 0, util.format("Should have no errors but had %d: %s",
- messages.length, util.inspect(messages)));
+ messages.length,
+ util.inspect(messages)));
assertASTDidntChange(result.beforeAST, result.afterAST);
}
}
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)));
+ item.errors,
+ item.errors === 1 ? "" : "s",
+ messages.length,
+ util.inspect(messages)));
} else {
assert.strictEqual(
- messages.length, item.errors.length,
- util.format(
+ 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)
+ item.errors.length,
+ item.errors.length === 1 ? "" : "s",
+ messages.length,
+ util.inspect(messages)
)
);
);
}
- // Rules that produce fixes must have `meta.fixable` property.
- if (result.output !== item.code) {
- assert.ok(
- hasOwnProperty(rule, "meta"),
- "Fixable rules should export a `meta.fixable` property."
- );
-
- // Linter throws if a rule that produced a fix has `meta` but doesn't have `meta.fixable`.
- }
-
assertASTDidntChange(result.beforeAST, result.afterAST);
}
RuleTester.describe(ruleName, () => {
RuleTester.describe("valid", () => {
test.valid.forEach(valid => {
- RuleTester.it(sanitize(typeof valid === "object" ? valid.code : valid), () => {
- testValidTemplate(valid);
- });
+ RuleTester[valid.only ? "itOnly" : "it"](
+ sanitize(typeof valid === "object" ? valid.name || valid.code : valid),
+ () => {
+ testValidTemplate(valid);
+ }
+ );
});
});
RuleTester.describe("invalid", () => {
test.invalid.forEach(invalid => {
- RuleTester.it(sanitize(invalid.code), () => {
- testInvalidTemplate(invalid);
- });
+ RuleTester[invalid.only ? "itOnly" : "it"](
+ sanitize(invalid.name || invalid.code),
+ () => {
+ testInvalidTemplate(invalid);
+ }
+ );
});
});
});
}
}
-RuleTester[DESCRIBE] = RuleTester[IT] = null;
+RuleTester[DESCRIBE] = RuleTester[IT] = RuleTester[IT_ONLY] = null;
module.exports = RuleTester;
docs: {
description: "enforce getter and setter pairs in objects and classes",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/accessor-pairs"
},
docs: {
description: "enforce linebreaks after opening and before closing array brackets",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/array-bracket-newline"
},
docs: {
description: "enforce consistent spacing inside array brackets",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/array-bracket-spacing"
},
docs: {
description: "enforce `return` statements in callbacks of array methods",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/array-callback-return"
},
docs: {
description: "enforce line breaks after each array element",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/array-element-newline"
},
docs: {
description: "require braces around arrow function bodies",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/arrow-body-style"
},
docs: {
description: "require parentheses around arrow function arguments",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/arrow-parens"
},
docs: {
description: "enforce consistent spacing before and after the arrow in arrow functions",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/arrow-spacing"
},
docs: {
description: "enforce the use of variables within the scope they are defined",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/block-scoped-var"
},
"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
docs: {
description: "disallow or enforce spaces inside of blocks after opening block and before closing block",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/block-spacing"
},
/**
* Gets the open brace token from a given node.
- * @param {ASTNode} node A BlockStatement/SwitchStatement node to get.
+ * @param {ASTNode} node A BlockStatement/StaticBlock/SwitchStatement node to get.
* @returns {Token} The token of the open brace.
*/
function getOpenBrace(node) {
}
return sourceCode.getLastToken(node, 1);
}
+
+ if (node.type === "StaticBlock") {
+ return sourceCode.getFirstToken(node, { skip: 1 }); // skip the `static` token
+ }
+
+ // "BlockStatement"
return sourceCode.getFirstToken(node);
}
}
/**
- * Reports invalid spacing style inside braces.
- * @param {ASTNode} node A BlockStatement/SwitchStatement node to get.
+ * Checks and reports invalid spacing style inside braces.
+ * @param {ASTNode} node A BlockStatement/StaticBlock/SwitchStatement node to check.
* @returns {void}
*/
function checkSpacingInsideBraces(node) {
return {
BlockStatement: checkSpacingInsideBraces,
+ StaticBlock: checkSpacingInsideBraces,
SwitchStatement: checkSpacingInsideBraces
};
}
docs: {
description: "enforce consistent brace style for blocks",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/brace-style"
},
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));
},
/**
* @fileoverview Enforce return after a callback.
* @author Jamund Ferguson
+ * @deprecated in ESLint v7.0.0
*/
"use strict";
docs: {
description: "require `return` statements after callbacks",
- category: "Node.js and CommonJS",
recommended: false,
url: "https://eslint.org/docs/rules/callback-return"
},
docs: {
description: "enforce camelcase naming convention",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/camelcase"
},
],
messages: {
- notCamelCase: "Identifier '{{name}}' is not in camel case."
+ notCamelCase: "Identifier '{{name}}' is not in camel case.",
+ notCamelCasePrivate: "#{{name}} is not in camel case."
}
},
create(context) {
-
const options = context.options[0] || {};
- let properties = options.properties || "";
+ const properties = options.properties === "never" ? "never" : "always";
const ignoreDestructuring = options.ignoreDestructuring;
const ignoreImports = options.ignoreImports;
const ignoreGlobals = options.ignoreGlobals;
const allow = options.allow || [];
- let globalScope;
-
- if (properties !== "always" && properties !== "never") {
- properties = "always";
- }
-
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
// contains reported nodes to avoid reporting twice on destructuring with shorthand notation
- const reported = [];
- const ALLOWED_PARENT_TYPES = new Set(["CallExpression", "NewExpression"]);
+ const reported = new Set();
/**
* Checks if a string contains an underscore and isn't all upper-case
* @private
*/
function isUnderscored(name) {
+ const nameBody = name.replace(/^_+|_+$/gu, "");
// if there's an underscore, it might be A_CONSTANT, which is okay
- return name.includes("_") && name !== name.toUpperCase();
+ return nameBody.includes("_") && nameBody !== nameBody.toUpperCase();
}
/**
}
/**
- * 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
+ * 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 isInsideObjectPattern(node) {
- let current = node;
-
- while (current) {
- const parent = current.parent;
+ function isGoodName(name) {
+ return !isUnderscored(name) || isAllowed(name);
+ }
- if (parent && parent.type === "Property" && parent.computed && parent.key === current) {
- return false;
- }
+ /**
+ * 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;
- if (current.type === "ObjectPattern") {
+ 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;
- }
- current = parent;
+ default:
+ return false;
}
-
- return false;
}
/**
- * Checks whether the given node represents assignment target property in destructuring.
- *
- * For examples:
- * ({a: b.foo} = c); // => true for `foo`
- * ([a.foo] = b); // => true for `foo`
- * ([a.foo = 1] = b); // => true for `foo`
- * ({...a.foo} = b); // => true for `foo`
- * @param {ASTNode} node An Identifier node to check
- * @returns {boolean} True if the node is an assignment target property in destructuring.
+ * Checks if a given binding identifier uses the original name as-is.
+ * - If it's in object destructuring, 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 isAssignmentTargetPropertyInDestructuring(node) {
- if (
- node.parent.type === "MemberExpression" &&
- node.parent.property === node &&
- !node.parent.computed
- ) {
- const effectiveParent = node.parent.parent;
-
- return (
- effectiveParent.type === "Property" &&
- effectiveParent.value === node.parent &&
- effectiveParent.parent.type === "ObjectPattern" ||
- effectiveParent.type === "ArrayPattern" ||
- effectiveParent.type === "RestElement" ||
- (
- effectiveParent.type === "AssignmentPattern" &&
- effectiveParent.left === node.parent
- )
- );
+ 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.value === valueNode &&
+ !parent.computed &&
+ parent.key.type === "Identifier" &&
+ parent.key.name === localName
+ );
+
+ case "ImportSpecifier":
+ return (
+ parent.local === node &&
+ parent.imported.name === localName
+ );
+
+ default:
+ return false;
}
- return false;
}
/**
- * 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.
+ * Reports an AST node as a rule violation.
+ * @param {ASTNode} node The node to report.
+ * @returns {void}
+ * @private
*/
- function isReferenceToGlobalVariable(node) {
- const variable = globalScope.set.get(node.name);
-
- return variable && variable.defs.length === 0 &&
- variable.references.some(ref => ref.identifier === node);
+ 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 }
+ });
}
/**
- * Checks whether the given node represents a reference to a property of an object in an object literal expression.
- * This allows to differentiate between a global variable that is allowed to be used as a reference, and the key
- * of the expressed object (which shouldn't be allowed).
- * @param {ASTNode} node `Identifier` node to check.
- * @returns {boolean} `true` if the node is a property name of an object literal expression
+ * Reports an identifier reference or a binding identifier.
+ * @param {ASTNode} node The `Identifier` node to report.
+ * @returns {void}
*/
- function isPropertyNameInObjectLiteral(node) {
- const parent = node.parent;
+ function reportReferenceId(node) {
- return (
- parent.type === "Property" &&
- parent.parent.type === "ObjectExpression" &&
- !parent.computed &&
- parent.key === 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;
+ }
- /**
- * Reports an AST node as a rule violation.
- * @param {ASTNode} node The node to report.
- * @returns {void}
- * @private
- */
- function report(node) {
- if (!reported.includes(node)) {
- reported.push(node);
- context.report({ node, messageId: "notCamelCase", data: { name: node.name } });
+ /*
+ * 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;
+ }
+
+ report(node);
}
return {
+ // Report camelcase of global variable references ------------------
Program() {
- globalScope = context.getScope();
- },
-
- Identifier(node) {
+ const scope = context.getScope();
- /*
- * Leading and trailing underscores are commonly used to flag
- * private/protected identifiers, strip them before checking if underscored
- */
- const name = node.name,
- nameIsUnderscored = isUnderscored(name.replace(/^_+|_+$/gu, "")),
- effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent;
+ if (!ignoreGlobals) {
- // First, we ignore the node if it match the ignore list
- if (isAllowed(name)) {
- return;
- }
+ // 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) {
- // Check if it's a global variable
- if (ignoreGlobals && isReferenceToGlobalVariable(node) && !isPropertyNameInObjectLiteral(node)) {
- return;
+ /*
+ * For backward compatibility, this rule reports read-only
+ * references as well.
+ */
+ reportReferenceId(reference.identifier);
+ }
+ }
}
- // MemberExpressions get special rules
- if (node.parent.type === "MemberExpression") {
+ // Undefined globals.
+ for (const reference of scope.through) {
+ const id = reference.identifier;
- // "never" check properties
- if (properties === "never") {
- return;
+ if (isGoodName(id.name)) {
+ continue;
}
- // Always report underscored object names
- if (node.parent.object.type === "Identifier" && node.parent.object.name === node.name && nameIsUnderscored) {
- report(node);
-
- // Report AssignmentExpressions only if they are the left side of the assignment
- } else if (effectiveParent.type === "AssignmentExpression" && nameIsUnderscored && (effectiveParent.right.type !== "MemberExpression" || effectiveParent.left.type === "MemberExpression" && effectiveParent.left.property.name === node.name)) {
- report(node);
+ /*
+ * For backward compatibility, this rule reports read-only
+ * references as well.
+ */
+ reportReferenceId(id);
+ }
+ },
- } else if (isAssignmentTargetPropertyInDestructuring(node) && nameIsUnderscored) {
- report(node);
+ // Report camelcase of declared variables --------------------------
+ [[
+ "VariableDeclaration",
+ "FunctionDeclaration",
+ "FunctionExpression",
+ "ArrowFunctionExpression",
+ "ClassDeclaration",
+ "ClassExpression",
+ "CatchClause"
+ ]](node) {
+ for (const variable of context.getDeclaredVariables(node)) {
+ if (isGoodName(variable.name)) {
+ continue;
}
+ const id = variable.identifiers[0];
- /*
- * Properties have their own rules, and
- * AssignmentPattern nodes can be treated like Properties:
- * e.g.: const { no_camelcased = false } = bar;
- */
- } else if (node.parent.type === "Property" || node.parent.type === "AssignmentPattern") {
-
- if (node.parent.parent && node.parent.parent.type === "ObjectPattern") {
- if (node.parent.shorthand && node.parent.value.left && nameIsUnderscored) {
- report(node);
- }
-
- const assignmentKeyEqualsValue = node.parent.key.name === node.parent.value.name;
-
- if (nameIsUnderscored && node.parent.computed) {
- report(node);
- }
+ // Report declaration.
+ if (!(ignoreDestructuring && equalsToOriginalName(id))) {
+ report(id);
+ }
- // prevent checking righthand side of destructured object
- if (node.parent.key === node && node.parent.value !== node) {
- return;
+ /*
+ * 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);
+ }
+ }
+ },
- const valueIsUnderscored = node.parent.value.name && nameIsUnderscored;
+ // 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" || 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);
+ },
- // ignore destructuring if the option is set, unless a new identifier is created
- if (valueIsUnderscored && !(assignmentKeyEqualsValue && ignoreDestructuring)) {
- report(node);
- }
+ // Report camelcase in import --------------------------------------
+ ImportDeclaration(node) {
+ for (const variable of context.getDeclaredVariables(node)) {
+ if (isGoodName(variable.name)) {
+ continue;
}
+ const id = variable.identifiers[0];
- // "never" check properties or always ignore destructuring
- if (properties === "never" || (ignoreDestructuring && isInsideObjectPattern(node))) {
- return;
+ // Report declaration.
+ if (!(ignoreImports && equalsToOriginalName(id))) {
+ report(id);
}
- // don't check right hand side of AssignmentExpression to prevent duplicate warnings
- if (nameIsUnderscored && !ALLOWED_PARENT_TYPES.has(effectiveParent.type) && !(node.parent.right === node)) {
- report(node);
+ /*
+ * For backward compatibility, report references as well.
+ * It looks unnecessary because declarations are reported.
+ */
+ for (const reference of variable.references) {
+ reportReferenceId(reference.identifier);
}
+ }
+ },
- // Check if it's an import specifier
- } else if (["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"].includes(node.parent.type)) {
-
- if (node.parent.type === "ImportSpecifier" && ignoreImports) {
- return;
- }
+ // Report camelcase in re-export -----------------------------------
+ [[
+ "ExportAllDeclaration > Identifier.exported",
+ "ExportSpecifier > Identifier.exported"
+ ]](node) {
+ if (isGoodName(node.name)) {
+ return;
+ }
+ report(node);
+ },
- // Report only if the local imported identifier is underscored
- if (
- node.parent.local &&
- node.parent.local.name === node.name &&
- nameIsUnderscored
- ) {
- report(node);
- }
+ // Report camelcase in labels --------------------------------------
+ [[
+ "LabeledStatement > Identifier.label",
- // Report anything that is underscored that isn't a CallExpression
- } else if (nameIsUnderscored && !ALLOWED_PARENT_TYPES.has(effectiveParent.type)) {
- report(node);
+ /*
+ * 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);
}
-
};
-
}
};
docs: {
description: "enforce or disallow capitalization of the first letter of a comment",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/capitalized-comments"
},
docs: {
description: "enforce that class methods utilize `this`",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/class-methods-use-this"
},
items: {
type: "string"
}
+ },
+ enforceForClassFields: {
+ type: "boolean",
+ default: true
}
},
additionalProperties: false
},
create(context) {
const config = Object.assign({}, context.options[0]);
+ const enforceForClassFields = config.enforceForClassFields !== false;
const exceptMethods = new Set(config.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.
* @private
*/
function enterFunction() {
- stack.push(false);
+ pushContext();
}
/**
* @private
*/
function isInstanceMethod(node) {
- return !node.static && node.kind !== "constructor" && node.type === "MethodDefinition";
+ switch (node.type) {
+ case "MethodDefinition":
+ return !node.static && node.kind !== "constructor";
+ case "PropertyDefinition":
+ return !node.static && enforceForClassFields;
+ default:
+ return false;
+ }
}
/**
* @private
*/
function isIncludedInstanceMethod(node) {
- return isInstanceMethod(node) &&
- (node.computed || !exceptMethods.has(node.key.name));
+ 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;
}
/**
* @private
*/
function exitFunction(node) {
- const methodUsesThis = stack.pop();
+ const methodUsesThis = popContext();
if (isIncludedInstanceMethod(node.parent) && !methodUsesThis) {
context.report({
node,
+ loc: astUtils.getFunctionHeadLoc(node, context.getSourceCode()),
messageId: "missingThis",
data: {
name: astUtils.getFunctionNameWithKind(node)
"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
+ Super: markThisUsed,
+ ...(
+ enforceForClassFields && {
+ "PropertyDefinition > ArrowFunctionExpression.value": enterFunction,
+ "PropertyDefinition > ArrowFunctionExpression.value:exit": exitFunction
+ }
+ )
};
}
};
docs: {
description: "require or disallow trailing commas",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/comma-dangle"
},
}
]
}
- ]
+ ],
+ additionalItems: false
},
messages: {
docs: {
description: "enforce consistent spacing before and after commas",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/comma-spacing"
},
docs: {
description: "enforce consistent comma style",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/comma-style"
},
* they are always valid regardless of an undefined item.
*/
if (astUtils.isCommaToken(commaToken)) {
- validateCommaItemSpacing(previousItemToken, commaToken,
- currentItemToken, reportItem);
+ validateCommaItemSpacing(previousItemToken, commaToken, currentItemToken, reportItem);
}
if (item) {
previousItemToken = tokenAfterItem
? sourceCode.getTokenBefore(tokenAfterItem)
: sourceCode.ast.tokens[sourceCode.ast.tokens.length - 1];
+ } else {
+ previousItemToken = currentItemToken;
}
});
docs: {
description: "enforce a maximum cyclomatic complexity allowed in a program",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/complexity"
},
// Helpers
//--------------------------------------------------------------------------
- // Using a stack to store complexity (handling nested functions)
- const fns = [];
+ // Using a stack to store complexity per code path
+ const complexities = [];
/**
- * When parsing a new function, store it in our function stack
- * @returns {void}
- * @private
- */
- function startFunction() {
- fns.push(1);
- }
-
- /**
- * Evaluate the node at the end of function
- * @param {ASTNode} node node to evaluate
- * @returns {void}
- * @private
- */
- function endFunction(node) {
- const name = upperCaseFirst(astUtils.getFunctionNameWithKind(node));
- const complexity = fns.pop();
-
- if (complexity > THRESHOLD) {
- context.report({
- node,
- messageId: "complex",
- data: { name, complexity, max: THRESHOLD }
- });
- }
- }
-
- /**
- * Increase the complexity of the function in context
+ * Increase the complexity of the code path in context
* @returns {void}
* @private
*/
function increaseComplexity() {
- if (fns.length) {
- fns[fns.length - 1]++;
- }
- }
-
- /**
- * Increase the switch complexity in context
- * @param {ASTNode} node node to evaluate
- * @returns {void}
- * @private
- */
- function increaseSwitchComplexity(node) {
-
- // Avoiding `default`
- if (node.test) {
- increaseComplexity();
- }
+ complexities[complexities.length - 1]++;
}
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
return {
- FunctionDeclaration: startFunction,
- FunctionExpression: startFunction,
- ArrowFunctionExpression: startFunction,
- "FunctionDeclaration:exit": endFunction,
- "FunctionExpression:exit": endFunction,
- "ArrowFunctionExpression:exit": endFunction,
+ 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,
ForInStatement: increaseComplexity,
ForOfStatement: increaseComplexity,
IfStatement: increaseComplexity,
- SwitchCase: increaseSwitchComplexity,
WhileStatement: increaseComplexity,
DoWhileStatement: increaseComplexity,
+ // Avoid `default`
+ "SwitchCase[test]": increaseComplexity,
+
+ // Logical assignment operators have short-circuiting behavior
AssignmentExpression(node) {
if (astUtils.isLogicalAssignmentOperator(node.operator)) {
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
+ }
+ });
+ }
}
};
docs: {
description: "enforce consistent spacing inside computed property brackets",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/computed-property-spacing"
},
};
if (enforceForClassMembers) {
- listeners.MethodDefinition = checkSpacing("key");
+ listeners.MethodDefinition =
+ listeners.PropertyDefinition = listeners.Property;
}
return listeners;
docs: {
description: "require `return` statements to either always or never specify values",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/consistent-return"
},
} else if (node.type === "ArrowFunctionExpression") {
// `=>` token
- loc = context.getSourceCode().getTokenBefore(node.body, astUtils.isArrowToken).loc.start;
+ loc = context.getSourceCode().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.start;
+ loc = node.parent.key.loc;
} else {
// Function name or `function` keyword.
- loc = (node.id || node).loc.start;
+ loc = (node.id || context.getSourceCode().getFirstToken(node)).loc;
}
if (!name) {
docs: {
description: "enforce consistent naming when capturing the current execution context",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/consistent-this"
},
* 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.
+ * @param {string} name the name of the alias that was incorrectly used.
* @returns {void}
*/
function reportBadAssignment(node, name) {
docs: {
description: "require `super()` calls in constructors",
- category: "ECMAScript 6",
recommended: true,
url: "https://eslint.org/docs/rules/constructor-super"
},
docs: {
description: "enforce consistent brace style for all control statements",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/curly"
},
return token.value === "else" && token.type === "Keyword";
}
- /**
- * Gets the `else` keyword token of a given `IfStatement` node.
- * @param {ASTNode} node A `IfStatement` node to get.
- * @returns {Token} The `else` keyword token.
- */
- function getElseKeyword(node) {
- return node.alternate && sourceCode.getFirstTokenBetween(node.consequent, node.alternate, isElseKeywordToken);
- }
-
/**
* Determines whether the given node has an `else` keyword token as the first token after.
* @param {ASTNode} node The node to check.
if (this.expected) {
context.report({
node,
- loc: (name !== "else" ? node : getElseKeyword(node)).loc.start,
+ loc: body.loc,
messageId: opts && opts.condition ? "missingCurlyAfterCondition" : "missingCurlyAfter",
data: {
name
} else {
context.report({
node,
- loc: (name !== "else" ? node : getElseKeyword(node)).loc.start,
+ loc: body.loc,
messageId: opts && opts.condition ? "unexpectedCurlyAfterCondition" : "unexpectedCurlyAfter",
data: {
name
docs: {
description: "enforce default clauses in switch statements to be last",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/default-case-last"
},
docs: {
description: "require `default` cases in `switch` statements",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/default-case"
},
/**
* Shortcut to get last element of array
- * @param {*[]} collection Array
- * @returns {*} Last element
+ * @param {*[]} collection Array
+ * @returns {any} Last element
*/
function last(collection) {
return collection[collection.length - 1];
docs: {
description: "enforce default parameters to be last",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/default-param-last"
},
create(context) {
- // eslint-disable-next-line jsdoc/require-description
/**
+ * Handler for function contexts.
* @param {ASTNode} node function node
* @returns {void}
*/
docs: {
description: "enforce consistent newlines before and after dots",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/dot-location"
},
docs: {
description: "enforce dot notation whenever possible",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/dot-notation"
},
// Don't perform any fixes if there are comments inside the brackets.
if (sourceCode.commentsExistBetween(leftBracket, rightBracket)) {
- return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
+ return;
}
// Replace the brackets by an identifier.
if (
!allowKeywords &&
!node.computed &&
+ node.property.type === "Identifier" &&
keywords.indexOf(String(node.property.name)) !== -1
) {
context.report({
// 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; // eslint-disable-line eslint-plugin/fixer-return -- false positive
+ return;
}
// Don't perform any fixes if there are comments between the dot and the property name.
if (sourceCode.commentsExistBetween(dotToken, node.property)) {
- return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
+ return;
}
// Replace the identifier to brackets.
docs: {
description: "require or disallow newline at the end of files",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/eol-last"
},
});
} else if (mode === "never" && endsWithNewline) {
+ const secondLastLine = sourceCode.lines[sourceCode.lines.length - 2];
+
// File is newline-terminated, but shouldn't be
context.report({
node,
- loc: location,
+ 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,
docs: {
description: "require the use of `===` and `!==`",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/eqeqeq"
},
/**
* Checks if an expression is a typeof expression
- * @param {ASTNode} node The node to check
+ * @param {ASTNode} node The node to check
* @returns {boolean} if the node is a typeof expression
*/
function isTypeOf(node) {
docs: {
description: "enforce \"for\" loop update clause moving the counter in the right direction.",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/for-direction"
},
docs: {
description: "require or disallow spacing between function identifiers and their invocations",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/func-call-spacing"
},
docs: {
description: "require function names to match the name of the variable or property to which they are assigned",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/func-name-matching"
},
const isProp = node.left.type === "MemberExpression";
const name = isProp ? astUtils.getStaticPropertyName(node.left) : node.left.name;
- if (node.right.id && isIdentifier(name) && shouldWarn(name, node.right.id.name)) {
+ if (node.right.id && name && isIdentifier(name) && shouldWarn(name, node.right.id.name)) {
report(node, name, node.right.id.name, isProp);
}
},
- Property(node) {
- if (node.value.type !== "FunctionExpression" || !node.value.id || node.computed && !isStringLiteral(node.key)) {
+ "Property, PropertyDefinition[value]"(node) {
+ if (!(node.value.type === "FunctionExpression" && node.value.id)) {
return;
}
- if (node.key.type === "Identifier") {
+ if (node.key.type === "Identifier" && !node.computed) {
const functionName = node.value.id.name;
let propertyName = node.key.name;
- if (considerPropertyDescriptor && propertyName === "value") {
+ 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];
docs: {
description: "require or disallow named `function` expressions",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/func-names"
},
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);
}
docs: {
description: "enforce the consistent use of either `function` declarations or expressions",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/func-style"
},
docs: {
description: "enforce line breaks between arguments of a function call",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/function-call-argument-newline"
},
docs: {
description: "enforce consistent line breaks inside function parentheses",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/function-paren-newline"
},
/**
* Gets the left paren and right paren tokens of a node.
* @param {ASTNode} node The node with parens
+ * @throws {TypeError} Unexecpted 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)
docs: {
description: "enforce consistent spacing around `*` operators in generator functions",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/generator-star-spacing"
},
docs: {
description: "enforce `return` statements in getters",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/getter-return"
},
/**
* @fileoverview Rule for disallowing require() outside of the top-level module context
* @author Jamund Ferguson
+ * @deprecated in ESLint v7.0.0
*/
"use strict";
docs: {
description: "require `require()` calls to be placed at top-level module scope",
- category: "Node.js and CommonJS",
recommended: false,
url: "https://eslint.org/docs/rules/global-require"
},
docs: {
description: "require grouped accessor pairs in object literals and classes",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/grouped-accessor-pairs"
},
docs: {
description: "require `for-in` loops to include an `if` statement",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/guard-for-in"
},
/**
* @fileoverview Ensure handling of errors when we know they exist.
* @author Jamund Ferguson
+ * @deprecated in ESLint v7.0.0
*/
"use strict";
docs: {
description: "require error handling in callbacks",
- category: "Node.js and CommonJS",
recommended: false,
url: "https://eslint.org/docs/rules/handle-callback-err"
},
* @fileoverview Rule that warns when identifier names that are
* specified in the configuration are used.
* @author Keith Cirkel (http://keithcirkel.co.uk)
+ * @deprecated in ESLint v7.5.0
*/
"use strict";
docs: {
description: "disallow specified identifiers",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/id-blacklist"
},
* @private
*/
function report(node) {
- if (!reportedNodes.has(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",
name: node.name
}
});
- reportedNodes.add(node);
+ reportedNodes.add(node.range.toString());
}
+
}
return {
}
/**
- * Checks whether the given node is a renamed identifier node in an ObjectPattern destructuring.
+ * Checks whether the given node is an ObjectPattern destructuring.
*
* Examples:
- * const { a : b } = foo; // node `a` is renamed node.
+ * const { a : b } = foo;
* @param {ASTNode} node `Identifier` node to check.
- * @returns {boolean} `true` if the node is a renamed node in an ObjectPattern destructuring.
+ * @returns {boolean} `true` if the node is in an ObjectPattern destructuring.
*/
-function isRenamedInDestructuring(node) {
+function isPropertyNameInDestructuring(node) {
const parent = node.parent;
return (
!parent.computed &&
parent.type === "Property" &&
parent.parent.type === "ObjectPattern" &&
- parent.value !== node &&
parent.key === node
)
);
}
-/**
- * Checks whether the given node represents shorthand definition of a property in an object literal.
- * @param {ASTNode} node `Identifier` node to check.
- * @returns {boolean} `true` if the node is a shorthand property definition.
- */
-function isShorthandPropertyDefinition(node) {
- const parent = node.parent;
-
- return (
- parent.type === "Property" &&
- parent.parent.type === "ObjectExpression" &&
- parent.shorthand
- );
-}
-
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
docs: {
description: "disallow specified identifiers",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/id-denylist"
},
uniqueItems: true
},
messages: {
- restricted: "Identifier '{{name}}' is restricted."
+ restricted: "Identifier '{{name}}' is restricted.",
+ restrictedPrivate: "Identifier '#{{name}}' is restricted."
}
},
parent.type !== "CallExpression" &&
parent.type !== "NewExpression" &&
!isRenamedImport(node) &&
- !isRenamedInDestructuring(node) &&
- !(
- isReferenceToGlobalVariable(node) &&
- !isShorthandPropertyDefinition(node)
- )
+ !isPropertyNameInDestructuring(node) &&
+ !isReferenceToGlobalVariable(node)
);
}
* @private
*/
function report(node) {
- if (!reportedNodes.has(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: "restricted",
+ messageId: isPrivate ? "restrictedPrivate" : "restricted",
data: {
name: node.name
}
});
- reportedNodes.add(node);
+ reportedNodes.add(node.range.toString());
}
}
globalScope = context.getScope();
},
- Identifier(node) {
+ [[
+ "Identifier",
+ "PrivateIdentifier"
+ ]](node) {
if (isRestricted(node.name) && shouldCheck(node)) {
report(node);
}
docs: {
description: "enforce minimum and maximum identifier lengths",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/id-length"
},
],
messages: {
tooShort: "Identifier name '{{name}}' is too short (< {{min}}).",
- tooLong: "Identifier name '{{name}}' is too long (> {{max}})."
+ tooShortPrivate: "Identifier name '#{{name}}' is too short (< {{min}}).",
+ tooLong: "Identifier name '{{name}}' is too long (> {{max}}).",
+ tooLongPrivate: "Identifier name #'{{name}}' is too long (> {{max}})."
}
},
const properties = options.properties !== "never";
const exceptions = new Set(options.exceptions);
const exceptionPatterns = (options.exceptionPatterns || []).map(pattern => new RegExp(pattern, "u"));
- const reportedNode = new Set();
+ const reportedNodes = new Set();
/**
* Checks if a string matches the provided exception patterns
Property(parent, node) {
if (parent.parent.type === "ObjectPattern") {
+ const isKeyAndValueSame = parent.value.name === parent.key.name;
+
return (
- parent.value !== parent.key && parent.value === node ||
- parent.value === parent.key && parent.key === node && properties
+ !isKeyAndValueSame && parent.value === node ||
+ isKeyAndValueSame && parent.key === node && properties
);
}
- return properties && !parent.computed && parent.key === node;
+ return properties && !parent.computed && parent.key.name === node.name;
},
ImportDefaultSpecifier: true,
RestElement: true,
ClassDeclaration: true,
FunctionDeclaration: true,
MethodDefinition: true,
+ PropertyDefinition: true,
CatchClause: true,
ArrayPattern: true
};
return {
- Identifier(node) {
+ [[
+ "Identifier",
+ "PrivateIdentifier"
+ ]](node) {
const name = node.name;
const parent = node.parent;
const isValidExpression = SUPPORTED_EXPRESSIONS[parent.type];
- if (isValidExpression && !reportedNode.has(node) && (isValidExpression === true || isValidExpression(parent, node))) {
- reportedNode.add(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 (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: isShort ? "tooShort" : "tooLong",
+ messageId,
data: { name, min: minLength, max: maxLength }
});
}
docs: {
description: "require identifiers to match a specified regular expression",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/id-match"
},
type: "boolean",
default: false
},
+ classFields: {
+ type: "boolean",
+ default: false
+ },
onlyDeclarations: {
type: "boolean",
default: false
}
],
messages: {
- notMatch: "Identifier '{{name}}' does not match the pattern '{{pattern}}'."
+ notMatch: "Identifier '{{name}}' does not match the pattern '{{pattern}}'.",
+ notMatchPrivate: "Identifier '#{{name}}' does not match the pattern '{{pattern}}'."
}
},
regexp = new RegExp(pattern, "u");
const options = context.options[1] || {},
- properties = !!options.properties,
+ checkProperties = !!options.properties,
+ checkClassFields = !!options.classFields,
onlyDeclarations = !!options.onlyDeclarations,
ignoreDestructuring = !!options.ignoreDestructuring;
//--------------------------------------------------------------------------
// contains reported nodes to avoid reporting twice on destructuring with shorthand notation
- const reported = new Map();
+ 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"]);
* @private
*/
function report(node) {
- if (!reported.has(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: "notMatch",
+ messageId,
data: {
name: node.name,
pattern
}
});
- reported.set(node, true);
+ reportedNodes.add(node.range.toString());
}
}
if (parent.type === "MemberExpression") {
- if (!properties) {
+ if (!checkProperties) {
return;
}
} else if (parent.type === "Property" || parent.type === "AssignmentPattern") {
if (parent.parent && parent.parent.type === "ObjectPattern") {
- if (parent.shorthand && parent.value.left && isInvalid(name)) {
-
+ if (!ignoreDestructuring && parent.shorthand && parent.value.left && isInvalid(name)) {
report(node);
}
}
// never check properties or always ignore destructuring
- if (!properties || (ignoreDestructuring && isInsideObjectPattern(node))) {
+ if (!checkProperties || (ignoreDestructuring && isInsideObjectPattern(node))) {
return;
}
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);
+ }
}
};
docs: {
description: "enforce the location of arrow function bodies",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/implicit-arrow-linebreak"
},
* This rule has been ported and modified from nodeca.
* @author Vitaly Puzrin
* @author Gyandeep Singh
+ * @deprecated in ESLint v4.0.0
*/
"use strict";
docs: {
description: "enforce consistent indentation",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/indent-legacy"
},
"NewExpression",
"ObjectExpression",
"ObjectPattern",
+ "PrivateIdentifier",
"Program",
"Property",
+ "PropertyDefinition",
"RestElement",
"ReturnStatement",
"SequenceExpression",
"SpreadElement",
+ "StaticBlock",
"Super",
"SwitchCase",
"SwitchStatement",
/**
* Inserts an entry into the tree.
* @param {number} key The entry's key
- * @param {*} value The entry's value
+ * @param {any} value The entry's value
* @returns {void}
*/
insert(key, value) {
*/
class TokenInfo {
- // eslint-disable-next-line jsdoc/require-description
/**
* @param {SourceCode} sourceCode A SourceCode object
*/
*/
class OffsetStorage {
- // eslint-disable-next-line jsdoc/require-description
/**
* @param {TokenInfo} tokenInfo a TokenInfo instance
* @param {number} indentSize The desired size of each indentation level
/**
* 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
+ * - **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
* 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
+ * - 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);`
docs: {
description: "enforce consistent indentation",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/indent"
},
},
additionalProperties: false
},
+ StaticBlock: {
+ type: "object",
+ properties: {
+ body: {
+ type: "integer",
+ minimum: 0
+ }
+ },
+ additionalProperties: false
+ },
CallExpression: {
type: "object",
properties: {
parameters: DEFAULT_PARAMETER_INDENT,
body: DEFAULT_FUNCTION_BODY_INDENT
},
+ StaticBlock: {
+ body: DEFAULT_FUNCTION_BODY_INDENT
+ },
CallExpression: {
arguments: DEFAULT_PARAMETER_INDENT
},
offsets.setDesiredOffset(questionMarkToken, firstToken, 1);
offsets.setDesiredOffset(colonToken, firstToken, 1);
- offsets.setDesiredOffset(firstConsequentToken, firstToken,
- firstConsequentToken.type === "Punctuator" &&
+ offsets.setDesiredOffset(firstConsequentToken, firstToken, firstConsequentToken.type === "Punctuator" &&
options.offsetTernaryExpressions ? 2 : 1);
/*
* 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" &&
+ offsets.setDesiredOffset(firstAlternateToken, firstToken, firstAlternateToken.type === "Punctuator" &&
options.offsetTernaryExpressions ? 2 : 1);
}
}
}
},
+ PropertyDefinition(node) {
+ const firstToken = sourceCode.getFirstToken(node);
+ const maybeSemicolonToken = sourceCode.getLastToken(node);
+ let keyLastToken = null;
+
+ // 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);
"use strict";
-/* eslint sort-keys: ["error", "asc"] */
+/* eslint sort-keys: ["error", "asc"] -- More readable for long list */
const { LazyLoadingRuleMap } = require("./utils/lazy-loading-rule-map");
"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-backreference": () => require("./no-useless-backreference"),
docs: {
description: "require or disallow initialization in variable declarations",
- category: "Variables",
recommended: false,
url: "https://eslint.org/docs/rules/init-declarations"
},
docs: {
description: "enforce the consistent use of either double or single quotes in JSX attributes",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/jsx-quotes"
},
docs: {
description: "enforce consistent spacing between keys and values in object literal properties",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/key-spacing"
},
* @returns {void}
*/
function report(property, side, whitespace, expected, mode) {
- const diff = whitespace.length - expected,
- 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;
+ const diff = whitespace.length - expected;
if ((
diff && mode === "strict" ||
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) {
/**
* Creates groups of properties.
- * @param {ASTNode} node ObjectExpression node being evaluated.
- * @returns {Array.<ASTNode[]>} Groups of property AST node lists.
+ * @param {ASTNode} node ObjectExpression node being evaluated.
+ * @returns {Array<ASTNode[]>} Groups of property AST node lists.
*/
function createGroups(node) {
if (node.properties.length === 1) {
/**
* Verifies spacing of property conforms to specified options.
- * @param {ASTNode} node Property node being evaluated.
+ * @param {ASTNode} node Property node being evaluated.
* @param {Object} lineOptions Configured singleLine or multiLine options
* @returns {void}
*/
/**
* Verifies vertical alignment, taking into account groups of properties.
- * @param {ASTNode} node ObjectExpression node being evaluated.
+ * @param {ASTNode} node ObjectExpression node being evaluated.
* @returns {void}
*/
function verifyAlignment(node) {
const NEXT_TOKEN_M = /^[{*]$/u;
const TEMPLATE_OPEN_PAREN = /\$\{$/u;
const TEMPLATE_CLOSE_PAREN = /^\}/u;
-const CHECK_TYPE = /^(?:JSXElement|RegularExpression|String|Template)$/u;
+const CHECK_TYPE = /^(?:JSXElement|RegularExpression|String|Template|PrivateIdentifier)$/u;
const KEYS = keywords.concat(["as", "async", "await", "from", "get", "let", "of", "set", "yield"]);
// check duplications.
docs: {
description: "enforce consistent spacing before and after keywords",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/keyword-spacing"
},
create(context) {
const sourceCode = context.getSourceCode();
+ const tokensToIgnore = new WeakSet();
+
/**
* Reports a given token if there are not space(s) before the token.
* @param {Token} token A token to report.
if (prevToken &&
(CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) &&
!isOpenParenOfTemplate(prevToken) &&
+ !tokensToIgnore.has(prevToken) &&
astUtils.isTokenOnSameLine(prevToken, token) &&
!sourceCode.isSpaceBetweenTokens(prevToken, 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)
) {
if (nextToken &&
(CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) &&
!isCloseParenOfTemplate(nextToken) &&
+ !tokensToIgnore.has(nextToken) &&
astUtils.isTokenOnSameLine(token, nextToken) &&
!sourceCode.isSpaceBetweenTokens(token, nextToken)
) {
if (nextToken &&
(CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) &&
!isCloseParenOfTemplate(nextToken) &&
+ !tokensToIgnore.has(nextToken) &&
astUtils.isTokenOnSameLine(token, nextToken) &&
sourceCode.isSpaceBetweenTokens(token, nextToken)
) {
*/
function checkSpacingForForInStatement(node) {
checkSpacingAroundFirstToken(node);
- checkSpacingAroundTokenBefore(node.right);
+
+ const inToken = sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken);
+ const previousToken = sourceCode.getTokenBefore(inToken);
+
+ if (previousToken.type !== "PrivateIdentifier") {
+ checkSpacingBefore(inToken);
+ }
+
+ checkSpacingAfter(inToken);
}
/**
} else {
checkSpacingAroundFirstToken(node);
}
- checkSpacingAround(sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken));
+
+ const ofToken = sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken);
+ const previousToken = sourceCode.getTokenBefore(ofToken);
+
+ if (previousToken.type !== "PrivateIdentifier") {
+ checkSpacingBefore(ofToken);
+ }
+
+ checkSpacingAfter(ofToken);
}
/**
* 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) {
// Others
ImportNamespaceSpecifier: checkSpacingForImportNamespaceSpecifier,
MethodDefinition: checkSpacingForProperty,
- Property: 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);
+ }
};
}
};
docs: {
description: "enforce position of line comments",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/line-comment-position"
},
docs: {
description: "enforce consistent linebreak style",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/linebreak-style"
},
docs: {
description: "require empty lines around comments",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/lines-around-comment"
},
/**
* Returns the parent node that contains the given token.
* @param {token} token The token to check.
- * @returns {ASTNode} The parent node that contains the given token.
+ * @returns {ASTNode|null} The parent node that contains the given token.
*/
function getParentNodeOfToken(token) {
- return sourceCode.getNodeByRangeIndex(token.range[0]);
+ 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;
}
/**
function isCommentAtParentStart(token, nodeType) {
const parent = getParentNodeOfToken(token);
- return parent && isParentNodeType(parent, nodeType) &&
- token.loc.start.line - parent.loc.start.line === 1;
+ if (parent && isParentNodeType(parent, nodeType)) {
+ const parentStartNodeOrToken = parent.type === "StaticBlock"
+ ? sourceCode.getFirstToken(parent, { skip: 1 }) // opening brace of the static block
+ : parent;
+
+ return token.loc.start.line - parentStartNodeOrToken.loc.start.line === 1;
+ }
+
+ return false;
}
/**
function isCommentAtParentEnd(token, nodeType) {
const parent = getParentNodeOfToken(token);
- return parent && isParentNodeType(parent, nodeType) &&
+ return !!parent && isParentNodeType(parent, nodeType) &&
parent.loc.end.line - token.loc.end.line === 1;
}
* @returns {boolean} True if the comment is at block start.
*/
function isCommentAtBlockStart(token) {
- return isCommentAtParentStart(token, "ClassBody") || isCommentAtParentStart(token, "BlockStatement") || isCommentAtParentStart(token, "SwitchCase");
+ return (
+ isCommentAtParentStart(token, "ClassBody") ||
+ isCommentAtParentStart(token, "BlockStatement") ||
+ isCommentAtParentStart(token, "StaticBlock") ||
+ isCommentAtParentStart(token, "SwitchCase")
+ );
}
/**
* @returns {boolean} True if the comment is at block end.
*/
function isCommentAtBlockEnd(token) {
- return isCommentAtParentEnd(token, "ClassBody") || isCommentAtParentEnd(token, "BlockStatement") || isCommentAtParentEnd(token, "SwitchCase") || isCommentAtParentEnd(token, "SwitchStatement");
+ return (
+ isCommentAtParentEnd(token, "ClassBody") ||
+ isCommentAtParentEnd(token, "BlockStatement") ||
+ isCommentAtParentEnd(token, "StaticBlock") ||
+ isCommentAtParentEnd(token, "SwitchCase") ||
+ isCommentAtParentEnd(token, "SwitchStatement")
+ );
}
/**
/**
* @fileoverview Require or disallow newlines around directives.
* @author Kai Cataldo
- * @deprecated
+ * @deprecated in ESLint v4.0.0
*/
"use strict";
docs: {
description: "require or disallow newlines around directives",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/lines-around-directive"
},
*/
"use strict";
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
docs: {
description: "require or disallow an empty line between class members",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/lines-between-class-members"
},
const sourceCode = context.getSourceCode();
+ /**
+ * 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)
+ );
+
+ 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.
for (let i = 0; i < body.length - 1; i++) {
const curFirst = sourceCode.getFirstToken(body[i]);
- const curLast = sourceCode.getLastToken(body[i]);
- const nextFirst = sourceCode.getFirstToken(body[i + 1]);
+ 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);
docs: {
description: "enforce a maximum number of classes per file",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/max-classes-per-file"
},
schema: [
{
- type: "integer",
- minimum: 1
+ oneOf: [
+ {
+ type: "integer",
+ minimum: 1
+ },
+ {
+ type: "object",
+ properties: {
+ ignoreExpressions: {
+ type: "boolean"
+ },
+ max: {
+ type: "integer",
+ minimum: 1
+ }
+ },
+ additionalProperties: false
+ }
+ ]
}
],
}
},
create(context) {
-
- const maxClasses = context.options[0] || 1;
+ const [option = {}] = context.options;
+ const [ignoreExpressions, max] = typeof option === "number"
+ ? [false, option || 1]
+ : [option.ignoreExpressions, option.max || 1];
let classCount = 0;
classCount = 0;
},
"Program:exit"(node) {
- if (classCount > maxClasses) {
+ if (classCount > max) {
context.report({
node,
messageId: "maximumExceeded",
data: {
classCount,
- max: maxClasses
+ max
}
});
}
},
- "ClassDeclaration, ClassExpression"() {
+ "ClassDeclaration"() {
classCount++;
+ },
+ "ClassExpression"() {
+ if (!ignoreExpressions) {
+ classCount++;
+ }
}
};
}
docs: {
description: "enforce a maximum depth that blocks can be nested",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/max-depth"
},
FunctionDeclaration: startFunction,
FunctionExpression: startFunction,
ArrowFunctionExpression: startFunction,
+ StaticBlock: startFunction,
IfStatement(node) {
if (node.parent.type !== "IfStatement") {
"FunctionDeclaration:exit": endFunction,
"FunctionExpression:exit": endFunction,
"ArrowFunctionExpression:exit": endFunction,
+ "StaticBlock:exit": endFunction,
"Program:exit": endFunction
};
docs: {
description: "enforce a maximum line length",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/max-len"
},
* 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 {*} value the value to add
+ * @param {any} value the value to add
* @returns {void}
* @private
*/
/**
* Given a list of comment nodes, return a map with numeric keys (source code line numbers) and comment token values.
* @param {Array} comments An array of comment nodes.
- * @returns {Map.<string,Node>} A map with numeric keys (source code line numbers) and comment token values.
+ * @returns {Map<string, Node>} A map with numeric keys (source code line numbers) and comment token values.
*/
function getCommentLineNumbers(comments) {
const map = new Map();
docs: {
description: "enforce a maximum number of lines of code in a function",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/max-lines-per-function"
},
docs: {
description: "enforce a maximum number of lines per file",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/max-lines"
},
return [];
}
- /**
- * Returns a new array formed by applying a given callback function to each element of the array, and then flattening the result by one level.
- * TODO(stephenwade): Replace this with array.flatMap when we drop support for Node v10
- * @param {any[]} array The array to process
- * @param {Function} fn The function to use
- * @returns {any[]} The result array
- */
- function flatMap(array, fn) {
- const mapped = array.map(fn);
- const flattened = [].concat(...mapped);
-
- return flattened;
- }
-
return {
"Program:exit"() {
let lines = sourceCode.lines.map((text, i) => ({
if (skipComments) {
const comments = sourceCode.getAllComments();
- const commentLines = flatMap(comments, comment => getLinesWithoutCode(comment));
+ const commentLines = comments.flatMap(getLinesWithoutCode);
lines = lines.filter(
l => !commentLines.includes(l.lineNumber)
docs: {
description: "enforce a maximum depth that callbacks can be nested",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/max-nested-callbacks"
},
docs: {
description: "enforce a maximum number of parameters in function definitions",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/max-params"
},
docs: {
description: "enforce a maximum number of statements allowed per line",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/max-statements-per-line"
},
docs: {
description: "enforce a maximum number of statements allowed in function blocks",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/max-statements"
},
function endFunction(node) {
const count = functionStack.pop();
+ /*
+ * This rule does not apply to class static blocks, but we have to track them so
+ * that stataments 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 {
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) {
docs: {
description: "enforce a particular style for multiline comments",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/multiline-comment-style"
},
docs: {
description: "enforce newlines between operands of ternary expressions",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/multiline-ternary"
},
* Ensure that if the key is provided, it must be an array.
* @param {Object} obj Object to check with `key`.
* @param {string} key Object key to check on `obj`.
- * @param {*} fallback If obj[key] is not present, this will be returned.
+ * @param {any} fallback If obj[key] is not present, this will be returned.
+ * @throws {TypeError} If key is not an own array type property of `obj`.
* @returns {string[]} Returns obj[key] if it's an Array, otherwise `fallback`
*/
function checkArray(obj, key, fallback) {
docs: {
description: "require constructor names to begin with a capital letter",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/new-cap"
},
docs: {
description: "enforce or disallow parentheses when invoking a constructor with no arguments",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/new-parens"
},
/**
* @fileoverview Rule to check empty newline after "var" statement
* @author Gopal Venkatesan
- * @deprecated
+ * @deprecated in ESLint v4.0.0
*/
"use strict";
docs: {
description: "require or disallow an empty line after variable declarations",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/newline-after-var"
},
/**
* 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
+ * @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;
/**
* @fileoverview Rule to require newlines before `return` statement
* @author Kai Cataldo
- * @deprecated
+ * @deprecated in ESLint v4.0.0
*/
"use strict";
docs: {
description: "require an empty line before `return` statements",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/newline-before-return"
},
docs: {
description: "require a newline after each call in a method chain",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/newline-per-chained-call"
},
* 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
+ * @param {ASTNode} node A MemberExpression node to get
* @returns {string} The prefix of the node.
*/
function getPrefix(node) {
docs: {
description: "disallow the use of `alert`, `confirm`, and `prompt`",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-alert"
},
docs: {
description: "disallow `Array` constructors",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/no-array-constructor"
},
docs: {
description: "disallow using an async function as a Promise executor",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-async-promise-executor"
},
docs: {
description: "disallow `await` inside of loops",
- category: "Possible Errors",
recommended: false,
url: "https://eslint.org/docs/rules/no-await-in-loop"
},
docs: {
description: "disallow bitwise operators",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/no-bitwise"
},
/**
* Reports an unexpected use of a bitwise operator.
- * @param {ASTNode} node Node which contains the bitwise operator.
+ * @param {ASTNode} node Node which contains the bitwise operator.
* @returns {void}
*/
function report(node) {
/**
* Checks if the given node has a bitwise operator.
- * @param {ASTNode} node The node to check.
+ * @param {ASTNode} node The node to check.
* @returns {boolean} Whether or not the node has a bitwise operator.
*/
function hasBitwiseOperator(node) {
/**
* Checks if exceptions were provided, e.g. `{ allow: ['~', '|'] }`.
- * @param {ASTNode} node The node to check.
+ * @param {ASTNode} node The node to check.
* @returns {boolean} Whether or not the node has a bitwise operator.
*/
function allowedOperator(node) {
/**
* Checks if the given bitwise operator is used for integer typecasting, i.e. "|0"
- * @param {ASTNode} node The node to check.
+ * @param {ASTNode} node The node to check.
* @returns {boolean} whether the node is used in integer typecasting.
*/
function isInt32Hint(node) {
/**
* Report if the given node contains a bitwise operator.
- * @param {ASTNode} node The node to check.
+ * @param {ASTNode} node The node to check.
* @returns {void}
*/
function checkNodeForBitwiseOperator(node) {
/**
* @fileoverview disallow use of the Buffer() constructor
* @author Teddy Katz
+ * @deprecated in ESLint v7.0.0
*/
"use strict";
docs: {
description: "disallow use of the `Buffer()` constructor",
- category: "Node.js and CommonJS",
recommended: false,
url: "https://eslint.org/docs/rules/no-buffer-constructor"
},
docs: {
description: "disallow the use of `arguments.caller` or `arguments.callee`",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-caller"
},
docs: {
description: "disallow lexical declarations in case clauses",
- category: "Best Practices",
recommended: true,
url: "https://eslint.org/docs/rules/no-case-declarations"
},
docs: {
description: "disallow `catch` clause parameters from shadowing variables in the outer scope",
- category: "Variables",
recommended: false,
url: "https://eslint.org/docs/rules/no-catch-shadow"
},
docs: {
description: "disallow reassigning class members",
- category: "ECMAScript 6",
recommended: true,
url: "https://eslint.org/docs/rules/no-class-assign"
},
docs: {
description: "disallow comparing against -0",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-compare-neg-zero"
},
docs: {
description: "disallow assignment operators in conditional expressions",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-cond-assign"
},
docs: {
description: "disallow arrow functions where they could be confused with comparisons",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/no-confusing-arrow"
},
docs: {
description: "disallow the use of `console`",
- category: "Possible Errors",
recommended: false,
url: "https://eslint.org/docs/rules/no-console"
},
docs: {
description: "disallow reassigning `const` variables",
- category: "ECMAScript 6",
recommended: true,
url: "https://eslint.org/docs/rules/no-const-assign"
},
docs: {
description: "disallow constant expressions in conditions",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-constant-condition"
},
docs: {
description: "disallow returning value from constructor",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-constructor-return"
},
docs: {
description: "disallow `continue` statements",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/no-continue"
},
docs: {
description: "disallow control characters in regular expressions",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-control-regex"
},
docs: {
description: "disallow the use of `debugger`",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-debugger"
},
docs: {
description: "disallow deleting variables",
- category: "Variables",
recommended: true,
url: "https://eslint.org/docs/rules/no-delete-var"
},
docs: {
description: "disallow division operators explicitly at the beginning of regular expressions",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-div-regex"
},
docs: {
description: "disallow duplicate arguments in `function` definitions",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-dupe-args"
},
docs: {
description: "disallow duplicate class members",
- category: "ECMAScript 6",
recommended: true,
url: "https://eslint.org/docs/rules/no-dupe-class-members"
},
},
// Reports the node if its name has been declared already.
- MethodDefinition(node) {
+ "MethodDefinition, PropertyDefinition"(node) {
const name = astUtils.getStaticPropertyName(node);
+ const kind = node.type === "MethodDefinition" ? node.kind : "field";
- if (name === null || node.kind === "constructor") {
+ if (name === null || kind === "constructor") {
return;
}
const state = getState(name, node.static);
let isDuplicate = false;
- if (node.kind === "get") {
+ if (kind === "get") {
isDuplicate = (state.init || state.get);
state.get = true;
- } else if (node.kind === "set") {
+ } else if (kind === "set") {
isDuplicate = (state.init || state.set);
state.set = true;
} else {
docs: {
description: "disallow duplicate conditions in if-else-if chains",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-dupe-else-if"
},
*/
class ObjectInfo {
- // eslint-disable-next-line jsdoc/require-description
/**
* @param {ObjectInfo|null} upper The information of the outer object.
* @param {ASTNode} node The ObjectExpression node of this information.
docs: {
description: "disallow duplicate keys in object literals",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-dupe-keys"
},
docs: {
description: "disallow duplicate case labels",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-duplicate-case"
},
docs: {
description: "disallow duplicate module imports",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/no-duplicate-imports"
},
docs: {
description: "disallow `else` blocks after `return` statements in `if` statements",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-else-return"
},
/*
* plain-English description of the following regexp:
* 0. `^` fix the match at the beginning of the string
- * 1. `\/`: the `/` that begins the regexp
- * 2. `([^\\[]|\\.|\[([^\\\]]|\\.)+\])*`: regexp contents; 0 or more of the following
- * 2.0. `[^\\[]`: any character that's not a `\` or a `[` (anything but escape sequences and character classes)
- * 2.1. `\\.`: an escape sequence
- * 2.2. `\[([^\\\]]|\\.)+\]`: a character class that isn't empty
- * 3. `\/` the `/` that ends the regexp
- * 4. `[gimuy]*`: optional regexp flags
- * 5. `$`: fix the match at the end of the string
+ * 1. `([^\\[]|\\.|\[([^\\\]]|\\.)+\])*`: regexp contents; 0 or more of the following
+ * 1.0. `[^\\[]`: any character that's not a `\` or a `[` (anything but escape sequences and character classes)
+ * 1.1. `\\.`: an escape sequence
+ * 1.2. `\[([^\\\]]|\\.)+\]`: a character class that isn't empty
+ * 2. `$`: fix the match at the end of the string
*/
-const regex = /^\/([^\\[]|\\.|\[([^\\\]]|\\.)+\])*\/[gimuys]*$/u;
+const regex = /^([^\\[]|\\.|\[([^\\\]]|\\.)+\])*$/u;
//------------------------------------------------------------------------------
// Rule Definition
docs: {
description: "disallow empty character classes in regular expressions",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-empty-character-class"
},
},
create(context) {
- const sourceCode = context.getSourceCode();
-
return {
-
- Literal(node) {
- const token = sourceCode.getFirstToken(node);
-
- if (token.type === "RegularExpression" && !regex.test(token.value)) {
+ "Literal[regex]"(node) {
+ if (!regex.test(node.regex.pattern)) {
context.report({ node, messageId: "unexpected" });
}
}
-
};
}
docs: {
description: "disallow empty functions",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-empty-function"
},
docs: {
description: "disallow empty destructuring patterns",
- category: "Best Practices",
recommended: true,
url: "https://eslint.org/docs/rules/no-empty-pattern"
},
docs: {
description: "disallow empty block statements",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-empty"
},
docs: {
description: "disallow `null` comparisons without type-checking operators",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-eq-null"
},
docs: {
description: "disallow the use of `eval()`",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-eval"
},
"FunctionExpression:exit": exitVarScope,
ArrowFunctionExpression: enterVarScope,
"ArrowFunctionExpression:exit": exitVarScope,
+ "PropertyDefinition > *.value": enterVarScope,
+ "PropertyDefinition > *.value:exit": exitVarScope,
+ StaticBlock: enterVarScope,
+ "StaticBlock:exit": exitVarScope,
ThisExpression(node) {
if (!isMember(node.parent, "eval")) {
docs: {
description: "disallow reassigning exceptions in `catch` clauses",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-ex-assign"
},
docs: {
description: "disallow extending native types",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-extend-native"
},
docs: {
description: "disallow unnecessary calls to `.bind()`",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-extra-bind"
},
docs: {
description: "disallow unnecessary boolean casts",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-extra-boolean-cast"
},
* For example, if the parent is `ConditionalExpression`, `previousNode` must be its `test` child.
* @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) {
docs: {
description: "disallow unnecessary labels",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-extra-label"
},
docs: {
description: "disallow unnecessary parentheses",
- category: "Possible Errors",
recommended: false,
url: "https://eslint.org/docs/rules/no-extra-parens"
},
CallExpression: checkCallNew,
- ClassBody(node) {
- node.body
- .filter(member => member.type === "MethodDefinition" && member.computed && member.key)
- .filter(member => hasExcessParensWithPrecedence(member.key, PRECEDENCE_OF_ASSIGNMENT_EXPR))
- .forEach(member => report(member.key));
- },
-
ConditionalExpression(node) {
if (isReturnAssignException(node)) {
return;
}
},
+ "MethodDefinition[computed=true]"(node) {
+ if (hasExcessParensWithPrecedence(node.key, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
+ report(node.key);
+ }
+ },
+
NewExpression: checkCallNew,
ObjectExpression(node) {
}
},
+ 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;
docs: {
description: "disallow unnecessary semicolons",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-extra-semi"
},
* @param {Node} node A MethodDefinition node of the start point.
* @returns {void}
*/
- MethodDefinition(node) {
+ "MethodDefinition, PropertyDefinition, StaticBlock"(node) {
checkForPartOfClassBody(sourceCode.getTokenAfter(node));
}
};
const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/iu;
/**
- * Checks whether or not a given node has a fallthrough comment.
- * @param {ASTNode} node A SwitchCase node to get comments.
+ * Checks whether or not a given case has a fallthrough comment.
+ * @param {ASTNode} caseWhichFallsThrough SwitchCase node which falls through.
+ * @param {ASTNode} subsequentCase The case after caseWhichFallsThrough.
* @param {RuleContext} context A rule context which stores comments.
* @param {RegExp} fallthroughCommentPattern A pattern to match comment to.
- * @returns {boolean} `true` if the node has a valid fallthrough comment.
+ * @returns {boolean} `true` if the case has a valid fallthrough comment.
*/
-function hasFallthroughComment(node, context, fallthroughCommentPattern) {
+function hasFallthroughComment(caseWhichFallsThrough, subsequentCase, context, fallthroughCommentPattern) {
const sourceCode = context.getSourceCode();
- const comment = sourceCode.getCommentsBefore(node).pop();
+
+ 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 && fallthroughCommentPattern.test(commentInBlock.value)) {
+ return true;
+ }
+ }
+
+ const comment = sourceCode.getCommentsBefore(subsequentCase).pop();
return Boolean(comment && fallthroughCommentPattern.test(comment.value));
}
docs: {
description: "disallow fallthrough of `case` statements",
- category: "Best Practices",
recommended: true,
url: "https://eslint.org/docs/rules/no-fallthrough"
},
* Checks whether or not there is a fallthrough comment.
* And reports the previous fallthrough node if that does not exist.
*/
- if (fallthroughCase && !hasFallthroughComment(node, context, fallthroughCommentPattern)) {
+ if (fallthroughCase && !hasFallthroughComment(fallthroughCase, node, context, fallthroughCommentPattern)) {
context.report({
messageId: node.test ? "case" : "default",
node
docs: {
description: "disallow leading or trailing decimal points in numeric literals",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-floating-decimal"
},
docs: {
description: "disallow reassigning `function` declarations",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-func-assign"
},
docs: {
description: "disallow assignments to native objects or read-only global variables",
- category: "Best Practices",
recommended: true,
url: "https://eslint.org/docs/rules/no-global-assign"
},
docs: {
description: "disallow shorthand type conversions",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-implicit-coercion"
},
docs: {
description: "disallow declarations in the global scope",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-implicit-globals"
},
docs: {
description: "disallow the use of `eval()`-like methods",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-implied-eval"
},
docs: {
description: "disallow assigning to imported bindings",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-import-assign"
},
docs: {
description: "disallow inline comments after code",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/no-inline-comments"
},
// Rule Definition
//------------------------------------------------------------------------------
-const validParent = new Set(["Program", "ExportNamedDeclaration", "ExportDefaultDeclaration"]);
+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.
+ * @param {ASTNode} node Node to search from.
+ * @returns {string} Description. One of "program", "function body", "class static block body".
+ */
+function getAllowedBodyDescription(node) {
+ let { parent } = node;
+
+ while (parent) {
+
+ if (parent.type === "StaticBlock") {
+ return "class static block body";
+ }
+
+ if (astUtils.isFunction(parent)) {
+ return "function body";
+ }
+
+ ({ parent } = parent);
+ }
+
+ return "program";
+}
+
module.exports = {
meta: {
type: "problem",
docs: {
description: "disallow variable or `function` declarations in nested blocks",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-inner-declarations"
},
return;
}
- const upperFunction = astUtils.getUpperFunction(parent);
-
context.report({
node,
messageId: "moveDeclToRoot",
data: {
type: (node.type === "FunctionDeclaration" ? "function" : "variable"),
- body: (upperFunction === null ? "program" : "function body")
+ body: getAllowedBodyDescription(node)
}
});
}
const RegExpValidator = require("regexpp").RegExpValidator;
const validator = new RegExpValidator();
-const validFlags = /[gimuys]/gu;
+const validFlags = /[dgimsuy]/gu;
const undefined1 = void 0;
//------------------------------------------------------------------------------
docs: {
description: "disallow invalid regular expression strings in `RegExp` constructors",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-invalid-regexp"
},
docs: {
description: "disallow `this` keywords outside of classes or class-like objects",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-invalid-this"
},
FunctionExpression: enterFunction,
"FunctionExpression:exit": exitFunction,
+ // Field initializers are implicit functions.
+ "PropertyDefinition > *.value": enterFunction,
+ "PropertyDefinition > *.value:exit": exitFunction,
+
+ // Class static blocks are implicit functions.
+ StaticBlock: enterFunction,
+ "StaticBlock:exit": exitFunction,
+
// Reports if `this` of the current context is invalid.
ThisExpression(node) {
const current = stack.getCurrent();
docs: {
description: "disallow irregular whitespace",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-irregular-whitespace"
},
docs: {
description: "disallow the use of the `__iterator__` property",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-iterator"
},
docs: {
description: "disallow labels that share a name with a variable",
- category: "Variables",
recommended: false,
url: "https://eslint.org/docs/rules/no-label-var"
},
docs: {
description: "disallow labeled statements",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-labels"
},
docs: {
description: "disallow unnecessary nested blocks",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-lone-blocks"
},
* @returns {void}
*/
function report(node) {
- const messageId = node.parent.type === "BlockStatement" ? "redundantNestedBlock" : "redundantBlock";
+ const messageId = node.parent.type === "BlockStatement" || node.parent.type === "StaticBlock"
+ ? "redundantNestedBlock"
+ : "redundantBlock";
context.report({
node,
*/
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.
loneBlocks.pop();
report(node);
} else if (
- node.parent.type === "BlockStatement" &&
+ (
+ node.parent.type === "BlockStatement" ||
+ node.parent.type === "StaticBlock"
+ ) &&
node.parent.body.length === 1
) {
report(node);
docs: {
description: "disallow `if` statements as the only statement in `else` blocks",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/no-lonely-if"
},
docs: {
description: "disallow function declarations that contain unsafe references inside loop statements",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-loop-func"
},
* - has a loop node in ancestors.
* - has any references which refers to an unsafe variable.
* @param {ASTNode} node The AST node to check.
- * @returns {boolean} Whether or not the node is within a loop.
+ * @returns {void}
*/
function checkForLoops(node) {
const loopNode = getContainingLoopNode(node);
docs: {
description: "disallow literal numbers that lose precision",
- category: "Possible Errors",
- recommended: false,
+ recommended: true,
url: "https://eslint.org/docs/rules/no-loss-of-precision"
},
schema: [],
docs: {
description: "disallow magic numbers",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-magic-numbers"
},
docs: {
description: "disallow characters which are made with multiple code points in character class syntax",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-misleading-character-class"
},
/**
* Checks whether any group which includes both given operator exists or not.
- * @param {Array.<string[]>} groups A list of groups to check.
+ * @param {Array<string[]>} groups A list of groups to check.
* @param {string} left An operator.
* @param {string} right Another operator.
* @returns {boolean} `true` if such group existed.
docs: {
description: "disallow mixed binary operators",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/no-mixed-operators"
},
],
messages: {
- unexpectedMixedOperator: "Unexpected mix of '{{leftOperator}}' and '{{rightOperator}}'."
+ unexpectedMixedOperator: "Unexpected mix of '{{leftOperator}}' and '{{rightOperator}}'. Use parentheses to clarify the intended order of operations."
}
},
/**
* @fileoverview Rule to enforce grouped require statements for Node.JS
* @author Raphael Pigulla
+ * @deprecated in ESLint v7.0.0
*/
"use strict";
docs: {
description: "disallow `require` calls to be mixed with regular variable declarations",
- category: "Node.js and CommonJS",
recommended: false,
url: "https://eslint.org/docs/rules/no-mixed-requires"
},
docs: {
description: "disallow mixed spaces and tabs for indentation",
- category: "Stylistic Issues",
recommended: true,
url: "https://eslint.org/docs/rules/no-mixed-spaces-and-tabs"
},
docs: {
description: "disallow use of chained assignment expressions",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/no-multi-assign"
},
const options = context.options[0] || {
ignoreNonDeclaration: false
};
- const targetParent = options.ignoreNonDeclaration ? ["VariableDeclarator"] : ["AssignmentExpression", "VariableDeclarator"];
+ const selectors = [
+ "VariableDeclarator > AssignmentExpression.init",
+ "PropertyDefinition > AssignmentExpression.value"
+ ];
+
+ if (!options.ignoreNonDeclaration) {
+ selectors.push("AssignmentExpression > AssignmentExpression.right");
+ }
return {
- AssignmentExpression(node) {
- if (targetParent.indexOf(node.parent.type) !== -1) {
- context.report({
- node,
- messageId: "unexpectedChain"
- });
- }
+ [selectors](node) {
+ context.report({
+ node,
+ messageId: "unexpectedChain"
+ });
}
};
docs: {
description: "disallow multiple spaces",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-multi-spaces"
},
docs: {
description: "disallow multiline strings",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-multi-str"
},
docs: {
description: "disallow multiple empty lines",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/no-multiple-empty-lines"
},
docs: {
description: "disallow assignments to native objects or read-only global variables",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-native-reassign"
},
docs: {
description: "disallow negated conditions",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/no-negated-condition"
},
docs: {
description: "disallow negating the left operand in `in` expressions",
- category: "Possible Errors",
recommended: false,
url: "https://eslint.org/docs/rules/no-negated-in-lhs"
},
docs: {
description: "disallow nested ternary expressions",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/no-nested-ternary"
},
"use strict";
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const callMethods = new Set(["apply", "bind", "call"]);
+
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
docs: {
description: "disallow `new` operators with the `Function` object",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-new-func"
},
variable.references.forEach(ref => {
const node = ref.identifier;
const { parent } = node;
+ let evalNode;
+
+ if (parent) {
+ if (node === parent.callee && (
+ parent.type === "NewExpression" ||
+ parent.type === "CallExpression"
+ )) {
+ evalNode = parent;
+ } else if (
+ parent.type === "MemberExpression" &&
+ node === 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 (
- parent &&
- (parent.type === "NewExpression" || parent.type === "CallExpression") &&
- node === parent.callee
- ) {
+ if (evalNode) {
context.report({
- node: parent,
+ node: evalNode,
messageId: "noFunctionConstructor"
});
}
docs: {
description: "disallow `Object` constructors",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/no-new-object"
},
/**
* @fileoverview Rule to disallow use of new operator with the `require` function
* @author Wil Moore III
+ * @deprecated in ESLint v7.0.0
*/
"use strict";
docs: {
description: "disallow `new` operators with calls to `require`",
- category: "Node.js and CommonJS",
recommended: false,
url: "https://eslint.org/docs/rules/no-new-require"
},
docs: {
description: "disallow `new` operators with the `Symbol` object",
- category: "ECMAScript 6",
recommended: true,
url: "https://eslint.org/docs/rules/no-new-symbol"
},
docs: {
description: "disallow `new` operators with the `String`, `Number`, and `Boolean` objects",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-new-wrappers"
},
docs: {
description: "disallow `new` operators outside of assignments or comparisons",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-new"
},
docs: {
description: "disallow `\\8` and `\\9` escape sequences in string literals",
- category: "Best Practices",
- recommended: false,
- url: "https://eslint.org/docs/rules/no-nonoctal-decimal-escape",
- suggestion: true
+ recommended: true,
+ url: "https://eslint.org/docs/rules/no-nonoctal-decimal-escape"
},
+ hasSuggestions: true,
+
schema: [],
messages: {
docs: {
description: "disallow calling global object properties as functions",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-obj-calls"
},
docs: {
description: "disallow octal escape sequences in string literals",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-octal-escape"
},
docs: {
description: "disallow octal literals",
- category: "Best Practices",
recommended: true,
url: "https://eslint.org/docs/rules/no-octal"
},
docs: {
description: "disallow reassigning `function` parameters",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-param-reassign"
},
/**
* @fileoverview Disallow string concatenation when using __dirname and __filename
* @author Nicholas C. Zakas
+ * @deprecated in ESLint v7.0.0
*/
"use strict";
docs: {
description: "disallow string concatenation with `__dirname` and `__filename`",
- category: "Node.js and CommonJS",
recommended: false,
url: "https://eslint.org/docs/rules/no-path-concat"
},
docs: {
description: "disallow the unary operators `++` and `--`",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/no-plusplus"
},
/**
* @fileoverview Disallow the use of process.env()
* @author Vignesh Anand
+ * @deprecated in ESLint v7.0.0
*/
"use strict";
docs: {
description: "disallow the use of `process.env`",
- category: "Node.js and CommonJS",
recommended: false,
url: "https://eslint.org/docs/rules/no-process-env"
},
/**
* @fileoverview Disallow the use of process.exit()
* @author Nicholas C. Zakas
+ * @deprecated in ESLint v7.0.0
*/
"use strict";
docs: {
description: "disallow the use of `process.exit()`",
- category: "Node.js and CommonJS",
recommended: false,
url: "https://eslint.org/docs/rules/no-process-exit"
},
docs: {
description: "disallow returning values from Promise executor functions",
- category: "Possible Errors",
recommended: false,
url: "https://eslint.org/docs/rules/no-promise-executor-return"
},
docs: {
description: "disallow the use of the `__proto__` property",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-proto"
},
docs: {
description: "disallow calling some `Object.prototype` methods directly on objects",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-prototype-builtins"
},
docs: {
description: "disallow variable redeclaration",
- category: "Best Practices",
recommended: true,
url: "https://eslint.org/docs/rules/no-redeclare"
},
FunctionExpression: checkForBlock,
ArrowFunctionExpression: checkForBlock,
+ StaticBlock: checkForBlock,
+
BlockStatement: checkForBlock,
ForStatement: checkForBlock,
ForInStatement: checkForBlock,
docs: {
description: "disallow multiple spaces in regular expressions",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-regex-spaces"
},
docs: {
description: "disallow specified names in exports",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/no-restricted-exports"
},
docs: {
description: "disallow specified global variables",
- category: "Variables",
recommended: false,
url: "https://eslint.org/docs/rules/no-restricted-globals"
},
messages: {
defaultMessage: "Unexpected use of '{{name}}'.",
- // eslint-disable-next-line eslint-plugin/report-message-format
+ // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
customMessage: "Unexpected use of '{{name}}'. {{customMessage}}"
}
},
docs: {
description: "disallow specified modules when loaded by `import`",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/no-restricted-imports"
},
messages: {
path: "'{{importSource}}' import is restricted from being used.",
- // eslint-disable-next-line eslint-plugin/report-message-format
+ // 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
+ // 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}}",
everything: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted.",
- // eslint-disable-next-line eslint-plugin/report-message-format
+ // 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
+ // 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}}"
},
? [{ matcher: ignore().add(restrictedPatterns) }]
: restrictedPatterns.map(({ group, message }) => ({ matcher: ignore().add(group), customMessage: message }));
- // if no imports are restricted we don"t need to check
+ // if no imports are restricted we don't need to check
if (Object.keys(restrictedPaths).length === 0 && restrictedPatternGroups.length === 0) {
return {};
}
/**
* @fileoverview Restrict usage of specified node modules.
* @author Christian Schulz
+ * @deprecated in ESLint v7.0.0
*/
"use strict";
docs: {
description: "disallow specified modules when loaded by `require`",
- category: "Node.js and CommonJS",
recommended: false,
url: "https://eslint.org/docs/rules/no-restricted-modules"
},
messages: {
defaultMessage: "'{{name}}' module is restricted from being used.",
- // eslint-disable-next-line eslint-plugin/report-message-format
+ // 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."
}
return memo;
}, {});
- // if no imports are restricted we don"t need to check
+ // if no imports are restricted we don't need to check
if (Object.keys(restrictedPaths).length === 0 && restrictedPatterns.length === 0) {
return {};
}
docs: {
description: "disallow certain properties on certain objects",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-restricted-properties"
},
},
messages: {
- // eslint-disable-next-line eslint-plugin/report-message-format
+ // 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
+ // 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}}"
}
},
docs: {
description: "disallow specified syntax",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/no-restricted-syntax"
},
},
messages: {
- // eslint-disable-next-line eslint-plugin/report-message-format
+ // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
restrictedSyntax: "{{message}}"
}
},
docs: {
description: "disallow assignment operators in `return` statements",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-return-assign"
},
docs: {
description: "disallow unnecessary `return await`",
- category: "Best Practices",
recommended: false,
* @fileoverview Rule to flag when using javascript: urls
* @author Ilya Volodin
*/
-/* jshint scripturl: true */
-/* eslint no-script-url: 0 */
+/* eslint no-script-url: 0 -- Code is checking to report such URLs */
"use strict";
docs: {
description: "disallow `javascript:` urls",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-script-url"
},
docs: {
description: "disallow assignments where both sides are exactly the same",
- category: "Best Practices",
recommended: true,
url: "https://eslint.org/docs/rules/no-self-assign"
},
docs: {
description: "disallow comparisons where both sides are exactly the same",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-self-compare"
},
docs: {
description: "disallow comma operators",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-sequences"
},
const parent = node.parent;
if (
+ (parent.type === "Property" || parent.type === "MethodDefinition") &&
parent.kind === "set" &&
parent.value === node
) {
docs: {
description: "disallow returning values from setters",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-setter-return"
},
docs: {
description: "disallow identifiers from shadowing restricted names",
- category: "Variables",
recommended: true,
url: "https://eslint.org/docs/rules/no-shadow-restricted-names"
},
docs: {
description: "disallow variable declarations from shadowing variables declared in the outer scope",
- category: "Variables",
recommended: false,
url: "https://eslint.org/docs/rules/no-shadow"
},
/**
* Check if variable name is allowed.
- * @param {ASTNode} variable The variable to check.
+ * @param {ASTNode} variable The variable to check.
* @returns {boolean} Whether or not the variable name is allowed.
*/
function isAllowed(variable) {
docs: {
description: "disallow spacing between function identifiers and their applications (deprecated)",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/no-spaced-func"
},
docs: {
description: "disallow sparse arrays",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-sparse-arrays"
},
/**
* @fileoverview Rule to check for properties whose identifier ends with the string Sync
* @author Matt DuVall<http://mattduvall.com/>
+ * @deprecated in ESLint v7.0.0
*/
-/* jshint node:true */
-
"use strict";
//------------------------------------------------------------------------------
docs: {
description: "disallow synchronous methods",
- category: "Node.js and CommonJS",
recommended: false,
url: "https://eslint.org/docs/rules/no-sync"
},
docs: {
description: "disallow all tabs",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/no-tabs"
},
docs: {
description: "disallow template literal placeholder syntax in regular strings",
- category: "Possible Errors",
recommended: false,
url: "https://eslint.org/docs/rules/no-template-curly-in-string"
},
docs: {
description: "disallow ternary operators",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/no-ternary"
},
docs: {
description: "disallow `this`/`super` before calling `super()` in constructors",
- category: "ECMAScript 6",
recommended: true,
url: "https://eslint.org/docs/rules/no-this-before-super"
},
docs: {
description: "disallow throwing literals as exceptions",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-throw-literal"
},
docs: {
description: "disallow trailing whitespace at the end of lines",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/no-trailing-spaces"
},
docs: {
description: "disallow initializing variables to `undefined`",
- category: "Variables",
recommended: false,
url: "https://eslint.org/docs/rules/no-undef-init"
},
docs: {
description: "disallow the use of undeclared variables unless mentioned in `/*global */` comments",
- category: "Variables",
recommended: true,
url: "https://eslint.org/docs/rules/no-undef"
},
docs: {
description: "disallow the use of `undefined` as an identifier",
- category: "Variables",
recommended: false,
url: "https://eslint.org/docs/rules/no-undefined"
},
docs: {
description: "disallow dangling underscores in identifiers",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/no-underscore-dangle"
},
node,
messageId: "unexpectedUnderscore",
data: {
- identifier
+ identifier: node.key.type === "PrivateIdentifier"
+ ? `#${identifier}`
+ : identifier
}
});
}
VariableDeclarator: checkForDanglingUnderscoreInVariableExpression,
MemberExpression: checkForDanglingUnderscoreInMemberExpression,
MethodDefinition: checkForDanglingUnderscoreInMethod,
+ PropertyDefinition: checkForDanglingUnderscoreInMethod,
Property: checkForDanglingUnderscoreInMethod,
FunctionExpression: checkForDanglingUnderscoreInFunction,
ArrowFunctionExpression: checkForDanglingUnderscoreInFunction
docs: {
description: "disallow confusing multiline expressions",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-unexpected-multiline"
},
docs: {
description: "disallow unmodified loop conditions",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-unmodified-loop-condition"
},
docs: {
description: "disallow ternary operators when simpler alternatives exist",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/no-unneeded-ternary"
},
docs: {
description: "disallow loops with a body that allows only one iteration",
- category: "Possible Errors",
recommended: false,
url: "https://eslint.org/docs/rules/no-unreachable-loop"
},
// Helpers
//------------------------------------------------------------------------------
+/**
+ * @typedef {Object} ConstructorInfo
+ * @property {ConstructorInfo | null} upper Info about the constructor that encloses this constructor.
+ * @property {boolean} hasSuperCall The flag about having `super()` expressions.
+ */
+
/**
* Checks whether or not a given variable declarator has the initializer.
* @param {ASTNode} node A VariableDeclarator node to check.
docs: {
description: "disallow unreachable code after `return`, `throw`, `continue`, and `break` statements",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-unreachable"
},
create(context) {
let currentCodePath = null;
+ /** @type {ConstructorInfo | null} */
+ let constructorInfo = null;
+
+ /** @type {ConsecutiveRange} */
const range = new ConsecutiveRange(context.getSourceCode());
/**
function reportIfUnreachable(node) {
let nextNode = null;
- if (node && currentCodePath.currentSegments.every(isUnreachable)) {
+ if (node && (node.type === "PropertyDefinition" || currentCodePath.currentSegments.every(isUnreachable))) {
// Store this statement to distinguish consecutive statements.
if (range.isEmpty) {
"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;
+ }
}
};
}
docs: {
description: "disallow control flow statements in `finally` blocks",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-unsafe-finally"
},
docs: {
description: "disallow negating the left operand of relational operators",
- category: "Possible Errors",
recommended: true,
- url: "https://eslint.org/docs/rules/no-unsafe-negation",
- suggestion: true
+ url: "https://eslint.org/docs/rules/no-unsafe-negation"
},
+ hasSuggestions: true,
+
schema: [
{
type: "object",
docs: {
description: "disallow use of optional chaining in contexts where the `undefined` value is not allowed",
- category: "Possible Errors",
- recommended: false,
+ recommended: true,
url: "https://eslint.org/docs/rules/no-unsafe-optional-chaining"
},
schema: [{
docs: {
description: "disallow unused expressions",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-unused-expressions"
},
allowTaggedTemplates = config.allowTaggedTemplates || false,
enforceForJSX = config.enforceForJSX || false;
- // eslint-disable-next-line jsdoc/require-description
/**
+ * Has AST suggesting a directive.
* @param {ASTNode} node any node
* @returns {boolean} whether the given node structurally represents a directive
*/
node.expression.type === "Literal" && typeof node.expression.value === "string";
}
- // eslint-disable-next-line jsdoc/require-description
/**
+ * 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
return list.slice();
}
- // eslint-disable-next-line jsdoc/require-description
/**
+ * 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
*/
return takeWhile(looksLikeDirective, node.body);
}
- // eslint-disable-next-line jsdoc/require-description
/**
+ * Detect if a Node is a directive.
* @param {ASTNode} node any node
* @param {ASTNode[]} ancestors the given node's ancestors
* @returns {boolean} whether the given node is considered a directive in its current position
const parent = ancestors[ancestors.length - 1],
grandparent = ancestors[ancestors.length - 2];
+ /**
+ * https://tc39.es/ecma262/#directive-prologue
+ *
+ * Only `FunctionBody`, `ScriptBody` and `ModuleBody` can have directive prologue.
+ * Class static blocks do not have directive prologue.
+ */
return (parent.type === "Program" || parent.type === "BlockStatement" &&
(/Function/u.test(grandparent.type))) &&
directives(parent).indexOf(node) >= 0;
docs: {
description: "disallow unused labels",
- category: "Best Practices",
recommended: true,
url: "https://eslint.org/docs/rules/no-unused-labels"
},
--- /dev/null
+/**
+ * @fileoverview Rule to flag declared but unused private class members
+ * @author Tim van der Lippe
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ type: "problem",
+
+ docs: {
+ description: "disallow unused private class members",
+ recommended: false,
+ url: "https://eslint.org/docs/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}`
+ }
+ });
+ }
+ }
+ };
+ }
+};
docs: {
description: "disallow unused variables",
- category: "Variables",
recommended: true,
url: "https://eslint.org/docs/rules/no-unused-vars"
},
);
}
+ /**
+ * 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[parent.expressions.length - 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.
function getRhsNode(ref, prevRhsNode) {
const id = ref.identifier;
const parent = id.parent;
- const grandparent = parent.parent;
const refScope = ref.from.variableScope;
const varScope = ref.resolved.scope.variableScope;
const canBeUsedLater = refScope !== varScope || astUtils.isInLoop(id);
}
if (parent.type === "AssignmentExpression" &&
- grandparent.type === "ExpressionStatement" &&
+ isUnusedExpression(parent) &&
id === parent.left &&
!canBeUsedLater
) {
);
}
- /**
- * 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[parent.expressions.length - 1] === node;
-
- if (!isLastExpression) {
- return true;
- }
- return isUnusedExpression(parent);
- }
-
- return false;
- }
-
/**
* Checks whether a given reference is a read to update itself or not.
* @param {eslint-scope.Reference} ref A reference to check.
}
/**
- * Checks whether or not a given variable is a function declaration.
- * @param {eslint-scope.Variable} variable A variable to check.
- * @returns {boolean} `true` if the variable is a function declaration.
+ * 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 isFunction(variable) {
- return variable.defs[0].type === "FunctionName";
+function isInRange(node, location) {
+ return node && node.range[0] <= location && location <= node.range[1];
}
/**
- * Checks whether or not a given variable is a class declaration in an upper function scope.
- * @param {eslint-scope.Variable} variable A variable to check.
- * @param {eslint-scope.Reference} reference A reference to check.
- * @returns {boolean} `true` if the variable is a class declaration.
+ * Checks whether or not a given location is inside of the range of a class static initializer.
+ * Static initializers are static blocks and initializers of static fields.
+ * @param {ASTNode} node `ClassBody` node to check static initializers.
+ * @param {number} location A location to check.
+ * @returns {boolean} `true` if the location is inside of a class static initializer.
*/
-function isOuterClass(variable, reference) {
- return (
- variable.defs[0].type === "ClassName" &&
- variable.scope.variableScope !== reference.from.variableScope
- );
+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)
+ )
+ ));
}
/**
- * Checks whether or not a given variable is a variable declaration in an upper function scope.
- * @param {eslint-scope.Variable} variable A variable to check.
- * @param {eslint-scope.Reference} reference A reference to check.
- * @returns {boolean} `true` if the variable is a variable declaration.
+ * Checks whether a given scope is the scope of a a class static initializer.
+ * Static initializers are static blocks and initializers of static fields.
+ * @param {eslint-scope.Scope} scope A scope to check.
+ * @returns {boolean} `true` if the scope is a class static initializer scope.
*/
-function isOuterVariable(variable, reference) {
- return (
- variable.defs[0].type === "Variable" &&
- variable.scope.variableScope !== reference.from.variableScope
- );
+function isClassStaticInitializerScope(scope) {
+ if (scope.type === "class-static-block") {
+ return true;
+ }
+
+ if (scope.type === "class-field-initializer") {
+
+ // `scope.block` is PropertyDefinition#value node
+ const propertyDefinition = scope.block.parent;
+
+ return propertyDefinition.static;
+ }
+
+ return false;
}
/**
- * 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.
+ * Checks whether a given reference is evaluated in an execution context
+ * that isn't the one where the variable it refers to is defined.
+ * Execution contexts are:
+ * - top-level
+ * - functions
+ * - class field initializers (implicit functions)
+ * - class static blocks (implicit functions)
+ * Static class field initializers and class static blocks are automatically run during the class definition evaluation,
+ * and therefore we'll consider them as a part of the parent execution context.
+ * Example:
+ *
+ * const x = 1;
+ *
+ * x; // returns `false`
+ * () => x; // returns `true`
+ *
+ * class C {
+ * field = x; // returns `true`
+ * static field = x; // returns `false`
+ *
+ * method() {
+ * x; // returns `true`
+ * }
+ *
+ * static method() {
+ * x; // returns `true`
+ * }
+ *
+ * static {
+ * x; // returns `false`
+ * }
+ * }
+ * @param {eslint-scope.Reference} reference A reference to check.
+ * @returns {boolean} `true` if the reference is from a separate execution context.
*/
-function isInRange(node, location) {
- return node && node.range[0] <= location && location <= node.range[1];
+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;
}
/**
- * Checks whether or not a given reference is inside of the initializers of a given variable.
+ * Checks whether or not a given reference is evaluated during the initialization of its variable.
*
* This returns `true` in the following cases:
*
* var {a = a} = obj
* for (var a in a) {}
* for (var a of a) {}
- * @param {Variable} variable A variable to check.
+ * var C = class { [C]; };
+ * var C = class { static foo = C; };
+ * var C = class { static { foo = C; } };
+ * class C extends C {}
+ * class C extends (class { static foo = C; }) {}
+ * class C { [C]; }
* @param {Reference} reference A reference to check.
- * @returns {boolean} `true` if the reference is inside of the initializers.
+ * @returns {boolean} `true` if the reference is evaluated during the initialization.
*/
-function isInInitializer(variable, reference) {
- if (variable.scope !== reference.from) {
+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;
}
- let node = variable.identifiers[0].parent;
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") {
docs: {
description: "disallow the use of variables before they are defined",
- category: "Variables",
recommended: false,
url: "https://eslint.org/docs/rules/no-use-before-define"
},
const options = parseOptions(context.options[0]);
/**
- * Determines whether a given use-before-define case should be reported according to the options.
- * @param {eslint-scope.Variable} variable The variable that gets used before being defined
- * @param {eslint-scope.Reference} reference The reference to the variable
- * @returns {boolean} `true` if the usage should be reported
+ * 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 isForbidden(variable, reference) {
- if (isFunction(variable)) {
- return options.functions;
+ function shouldCheck(reference) {
+ if (reference.init) {
+ return false;
}
- if (isOuterClass(variable, reference)) {
- return options.classes;
+
+ const variable = reference.resolved;
+
+ if (!variable || variable.defs.length === 0) {
+ return false;
}
- if (isOuterVariable(variable, reference)) {
- return options.variables;
+
+ 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 variables in a given scope.
- * @param {Scope} scope The scope object.
+ * Finds and validates all references in a given scope and its child scopes.
+ * @param {eslint-scope.Scope} scope The scope object.
* @returns {void}
- * @private
*/
- function findVariablesInScope(scope) {
- scope.references.forEach(reference => {
+ function checkReferencesInScope(scope) {
+ scope.references.filter(shouldCheck).forEach(reference => {
const variable = reference.resolved;
+ const definitionIdentifier = variable.defs[0].name;
- /*
- * Skips when the reference is:
- * - initialization's.
- * - referring to an undefined variable.
- * - referring to a global environment variable (there're no identifiers).
- * - located preceded by the variable (except in initializers).
- * - allowed by options.
- */
- if (reference.init ||
- !variable ||
- variable.identifiers.length === 0 ||
- (variable.identifiers[0].range[1] < reference.identifier.range[1] && !isInInitializer(variable, reference)) ||
- !isForbidden(variable, reference)
+ if (
+ reference.identifier.range[1] < definitionIdentifier.range[1] ||
+ isEvaluatedDuringInitialization(reference)
) {
- return;
+ context.report({
+ node: reference.identifier,
+ messageId: "usedBeforeDefined",
+ data: reference.identifier
+ });
}
-
- // Reports.
- context.report({
- node: reference.identifier,
- messageId: "usedBeforeDefined",
- data: reference.identifier
- });
});
- scope.childScopes.forEach(findVariablesInScope);
+ scope.childScopes.forEach(checkReferencesInScope);
}
return {
Program() {
- findVariablesInScope(context.getScope());
+ checkReferencesInScope(context.getScope());
}
};
}
docs: {
description: "disallow useless backreferences in regular expressions",
- category: "Possible Errors",
- recommended: false,
+ recommended: true,
url: "https://eslint.org/docs/rules/no-useless-backreference"
},
docs: {
description: "disallow unnecessary calls to `.call()` and `.apply()`",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-useless-call"
},
docs: {
description: "disallow unnecessary `catch` clauses",
- category: "Best Practices",
recommended: true,
url: "https://eslint.org/docs/rules/no-useless-catch"
},
const astUtils = require("./utils/ast-utils");
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Determines whether the computed key syntax is unnecessarily used for the given node.
+ * In particular, it determines whether removing the square brackets and using the content between them
+ * directly as the key (e.g. ['foo'] -> 'foo') would produce valid syntax and preserve the same behavior.
+ * Valid non-computed keys are only: identifiers, number literals and string literals.
+ * Only literals can preserve the same behavior, with a few exceptions for specific node types:
+ * Property
+ * - { ["__proto__"]: foo } defines a property named "__proto__"
+ * { "__proto__": foo } defines object's prototype
+ * PropertyDefinition
+ * - class C { ["constructor"]; } defines an instance field named "constructor"
+ * class C { "constructor"; } produces a parsing error
+ * - class C { static ["constructor"]; } defines a static field named "constructor"
+ * class C { static "constructor"; } produces a parsing error
+ * - class C { static ["prototype"]; } produces a runtime error (doesn't break the whole script)
+ * class C { static "prototype"; } produces a parsing error (breaks the whole script)
+ * MethodDefinition
+ * - class C { ["constructor"]() {} } defines a prototype method named "constructor"
+ * class C { "constructor"() {} } defines the constructor
+ * - class C { static ["prototype"]() {} } produces a runtime error (doesn't break the whole script)
+ * class C { static "prototype"() {} } produces a parsing error (breaks the whole script)
+ * @param {ASTNode} node The node to check. It can be `Property`, `PropertyDefinition` or `MethodDefinition`.
+ * @throws {Error} (Unreachable.)
+ * @returns {void} `true` if the node has useless computed key.
+ */
+function hasUselessComputedKey(node) {
+ if (!node.computed) {
+ return false;
+ }
+
+ const { key } = node;
+
+ if (key.type !== "Literal") {
+ return false;
+ }
+
+ const { value } = key;
+
+ if (typeof value !== "number" && typeof value !== "string") {
+ return false;
+ }
+
+ switch (node.type) {
+ case "Property":
+ return value !== "__proto__";
+
+ case "PropertyDefinition":
+ if (node.static) {
+ return value !== "constructor" && value !== "prototype";
+ }
+
+ return value !== "constructor";
+
+ case "MethodDefinition":
+ if (node.static) {
+ return value !== "prototype";
+ }
+
+ return value !== "constructor";
+
+ /* istanbul ignore next */
+ default:
+ throw new Error(`Unexpected node type: ${node.type}`);
+ }
+
+}
+
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
docs: {
description: "disallow unnecessary computed property keys in objects and classes",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/no-useless-computed-key"
},
* @returns {void}
*/
function check(node) {
- if (!node.computed) {
- return;
- }
-
- const key = node.key,
- nodeType = typeof key.value;
-
- let allowedKey;
-
- if (node.type === "MethodDefinition") {
- allowedKey = node.static ? "prototype" : "constructor";
- } else {
- allowedKey = "__proto__";
- }
+ if (hasUselessComputedKey(node)) {
+ const { key } = node;
- if (key.type === "Literal" && (nodeType === "string" || nodeType === "number") && key.value !== allowedKey) {
context.report({
node,
messageId: "unnecessarilyComputedProperty",
return {
Property: check,
- MethodDefinition: enforceForClassMembers ? check : noop
+ MethodDefinition: enforceForClassMembers ? check : noop,
+ PropertyDefinition: enforceForClassMembers ? check : noop
};
}
};
docs: {
description: "disallow unnecessary concatenation of literals or template literals",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-useless-concat"
},
docs: {
description: "disallow unnecessary constructors",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/no-useless-constructor"
},
* @returns {Object[]} A list of characters, each with info on escaping and whether they're in a character class.
* @example
*
- * parseRegExp('a\\b[cd-]')
+ * parseRegExp("a\\b[cd-]");
*
- * returns:
+ * // returns:
* [
- * {text: 'a', index: 0, escaped: false, inCharClass: false, startsCharClass: false, endsCharClass: false},
- * {text: 'b', index: 2, escaped: true, inCharClass: false, startsCharClass: false, endsCharClass: false},
- * {text: 'c', index: 4, escaped: false, inCharClass: true, startsCharClass: true, endsCharClass: false},
- * {text: 'd', index: 5, escaped: false, inCharClass: true, startsCharClass: false, endsCharClass: false},
- * {text: '-', index: 6, escaped: false, inCharClass: true, startsCharClass: false, endsCharClass: false}
- * ]
+ * { text: "a", index: 0, escaped: false, inCharClass: false, startsCharClass: false, endsCharClass: false },
+ * { text: "b", index: 2, escaped: true, inCharClass: false, startsCharClass: false, endsCharClass: false },
+ * { text: "c", index: 4, escaped: false, inCharClass: true, startsCharClass: true, endsCharClass: false },
+ * { text: "d", index: 5, escaped: false, inCharClass: true, startsCharClass: false, endsCharClass: false },
+ * { text: "-", index: 6, escaped: false, inCharClass: true, startsCharClass: false, endsCharClass: false }
+ * ];
+ *
*/
function parseRegExp(regExpText) {
const charList = [];
docs: {
description: "disallow unnecessary escape characters",
- category: "Best Practices",
recommended: true,
- url: "https://eslint.org/docs/rules/no-useless-escape",
- suggestion: true
+ url: "https://eslint.org/docs/rules/no-useless-escape"
},
+ hasSuggestions: true,
+
messages: {
unnecessaryEscape: "Unnecessary escape character: \\{{character}}.",
removeEscape: "Remove the `\\`. This maintains the current functionality.",
docs: {
description: "disallow renaming import, export, and destructured assignments to the same name",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/no-useless-rename"
},
docs: {
description: "disallow redundant return statements",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-useless-return"
},
docs: {
description: "require `let` or `const` instead of `var`",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/no-var"
},
docs: {
description: "disallow `void` operators",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-void"
},
docs: {
description: "disallow specified warning terms in comments",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/no-warning-comments"
},
docs: {
description: "disallow whitespace before properties",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/no-whitespace-before-property"
},
docs: {
description: "disallow `with` statements",
- category: "Best Practices",
recommended: true,
url: "https://eslint.org/docs/rules/no-with"
},
docs: {
description: "enforce the location of single-line statements",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/nonblock-statement-body-position"
},
docs: {
description: "enforce consistent line breaks after opening and before closing braces",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/object-curly-newline"
},
docs: {
description: "enforce consistent spacing inside braces",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/object-curly-spacing"
},
docs: {
description: "enforce placing object properties on separate lines",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/object-property-newline"
},
docs: {
description: "require or disallow method and property shorthand syntax for object literals",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/object-shorthand"
},
* @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.
+ * @param {ASTNode} node Any AST node.
* @returns {boolean} `true` if it is a string literal.
*/
function isStringLiteral(node) {
* @param {ASTNode} property Property AST node
* @returns {boolean} True if the property is considered shorthand, false if not.
* @private
- *
*/
function isShorthand(property) {
* @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;
/**
* 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
+ * @param {ASTNode} node Property AST node
+ * @param {boolean} checkRedundancy Whether to check longform redundancy
* @returns {void}
- *
*/
function checkConsistency(node, checkRedundancy) {
docs: {
description: "require or disallow newlines around variable declarations",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/one-var-declaration-per-line"
},
docs: {
description: "enforce variables to be declared either together or separately in functions",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/one-var"
},
/**
* Determines the current scope (function or block)
- * @param {string} statementType node.kind, one of: "var", "let", or "const"
+ * @param {string} statementType node.kind, one of: "var", "let", or "const"
* @returns {Object} The scope associated with statementType
*/
function getCurrentScope(statementType) {
FunctionDeclaration: startFunction,
FunctionExpression: startFunction,
ArrowFunctionExpression: startFunction,
+ StaticBlock: startFunction, // StaticBlock creates a new scope for `var` variables
+
BlockStatement: startBlock,
ForStatement: startBlock,
ForInStatement: startBlock,
"ForInStatement:exit": endBlock,
"SwitchStatement:exit": endBlock,
"BlockStatement:exit": endBlock,
+
"Program:exit": endFunction,
"FunctionDeclaration:exit": endFunction,
"FunctionExpression:exit": endFunction,
- "ArrowFunctionExpression:exit": endFunction
+ "ArrowFunctionExpression:exit": endFunction,
+ "StaticBlock:exit": endFunction
};
}
/**
* Checks whether an operator is commutative and has an operator assignment
* shorthand form.
- * @param {string} operator Operator to check.
- * @returns {boolean} True if the operator is commutative and has a
+ * @param {string} operator Operator to check.
+ * @returns {boolean} True if the operator is commutative and has a
* shorthand form.
*/
function isCommutativeOperatorWithShorthand(operator) {
/**
* Checks whether an operator is not commutative and has an operator assignment
* shorthand form.
- * @param {string} operator Operator to check.
- * @returns {boolean} True if the operator is not commutative and has
+ * @param {string} operator Operator to check.
+ * @returns {boolean} True if the operator is not commutative and has
* a shorthand form.
*/
function isNonCommutativeOperatorWithShorthand(operator) {
docs: {
description: "require or disallow assignment operator shorthand where possible",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/operator-assignment"
},
fixable: "code",
messages: {
- replaced: "Assignment can be replaced with operator assignment.",
- unexpected: "Unexpected operator assignment shorthand."
+ replaced: "Assignment (=) can be replaced with operator assignment ({{operator}}=).",
+ unexpected: "Unexpected operator assignment ({{operator}}=) shorthand."
}
},
/**
* Ensures that an assignment uses the shorthand form where possible.
- * @param {ASTNode} node An AssignmentExpression node.
+ * @param {ASTNode} node An AssignmentExpression node.
* @returns {void}
*/
function verify(node) {
context.report({
node,
messageId: "replaced",
+ data: { operator },
fix(fixer) {
if (canBeFixed(left) && canBeFixed(expr.left)) {
const equalsToken = getOperatorToken(node);
*/
context.report({
node,
- messageId: "replaced"
+ messageId: "replaced",
+ data: { operator }
});
}
}
/**
* Warns if an assignment expression uses operator assignment shorthand.
- * @param {ASTNode} node An AssignmentExpression node.
+ * @param {ASTNode} node An AssignmentExpression node.
* @returns {void}
*/
function prohibit(node) {
context.report({
node,
messageId: "unexpected",
+ data: { operator: node.operator },
fix(fixer) {
if (canBeFixed(node.left)) {
const firstToken = sourceCode.getFirstToken(node);
docs: {
description: "enforce consistent linebreak style for operators",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/operator-linebreak"
},
/**
* Checks the operator placement
* @param {ASTNode} node The node to check
- * @param {ASTNode} leftSide The node that comes before the operator in `node`
+ * @param {ASTNode} rightSide The node that comes after the operator in `node`
+ * @param {string} operator The operator
* @private
* @returns {void}
*/
- function validateNode(node, leftSide) {
+ function validateNode(node, rightSide, operator) {
/*
- * When the left part of a binary expression is a single expression wrapped in
- * parentheses (ex: `(a) + b`), leftToken will be the last token of the expression
- * and operatorToken will be the closing parenthesis.
- * The leftToken should be the last closing parenthesis, and the operatorToken
- * should be the token right after that.
+ * 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.getTokenAfter(leftSide, astUtils.isNotClosingParenToken);
+ const operatorToken = sourceCode.getTokenBefore(rightSide, token => token.value === operator);
const leftToken = sourceCode.getTokenBefore(operatorToken);
const rightToken = sourceCode.getTokenAfter(operatorToken);
- const operator = operatorToken.value;
const operatorStyleOverride = styleOverrides[operator];
const style = operatorStyleOverride || globalStyle;
const fix = getFixer(operatorToken, style);
* @returns {void}
*/
function validateBinaryExpression(node) {
- validateNode(node, node.left);
+ validateNode(node, node.right, node.operator);
}
//--------------------------------------------------------------------------
AssignmentExpression: validateBinaryExpression,
VariableDeclarator(node) {
if (node.init) {
- validateNode(node, node.id);
+ validateNode(node, node.init, "=");
+ }
+ },
+ PropertyDefinition(node) {
+ if (node.value) {
+ validateNode(node, node.value, "=");
}
},
ConditionalExpression(node) {
- validateNode(node, node.test);
- validateNode(node, node.consequent);
+ validateNode(node, node.consequent, "?");
+ validateNode(node, node.alternate, ":");
}
};
}
docs: {
description: "require or disallow padding within blocks",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/padded-blocks"
},
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 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;
}
checkPadding(node);
};
+ rule.StaticBlock = rule.BlockStatement;
}
if (Object.prototype.hasOwnProperty.call(options, "classes")) {
docs: {
description: "require or disallow padding lines between statements",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/padding-line-between-statements"
},
Program: enterScope,
BlockStatement: enterScope,
SwitchStatement: enterScope,
+ StaticBlock: enterScope,
"Program:exit": exitScope,
"BlockStatement:exit": exitScope,
"SwitchStatement:exit": exitScope,
+ "StaticBlock:exit": exitScope,
":statement": verify,
/**
* Checks whether or not a given node is a callback.
* @param {ASTNode} node A node to check.
+ * @throws {Error} (Unreachable.)
* @returns {Object}
* {boolean} retv.isCallback - `true` if the node is a callback.
* {boolean} retv.isLexicalThis - `true` if the node is with `.bind(this)`.
docs: {
description: "require using arrow functions for callbacks",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/prefer-arrow-callback"
},
* 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; // eslint-disable-line eslint-plugin/fixer-return -- false positive
+ return;
}
// Remove `.bind(this)` if exists.
* E.g. `(foo || function(){}).bind(this)`
*/
if (memberNode.type !== "MemberExpression") {
- return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
+ return;
}
const callNode = memberNode.parent;
* ^^^^^^^^^^^^
*/
if (astUtils.isParenthesised(sourceCode, memberNode)) {
- return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
+ return;
}
// If comments exist in the `.bind(this)`, don't remove those.
if (sourceCode.commentsExistBetween(firstTokenToRemove, lastTokenToRemove)) {
- return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
+ return;
}
yield fixer.removeRange([firstTokenToRemove.range[0], lastTokenToRemove.range[1]]);
//------------------------------------------------------------------------------
const PATTERN_TYPE = /^(?:.+?Pattern|RestElement|SpreadProperty|ExperimentalRestProperty|Property)$/u;
-const DECLARATION_HOST_TYPE = /^(?:Program|BlockStatement|SwitchCase)$/u;
+const DECLARATION_HOST_TYPE = /^(?:Program|BlockStatement|StaticBlock|SwitchCase)$/u;
const DESTRUCTURING_HOST_TYPE = /^(?:VariableDeclarator|AssignmentExpression)$/u;
/**
docs: {
description: "require `const` declarations for variables that are never reassigned after declared",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/prefer-const"
},
docs: {
description: "require destructuring from arrays and/or objects",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/prefer-destructuring"
},
// Helpers
//--------------------------------------------------------------------------
- // eslint-disable-next-line jsdoc/require-description
/**
+ * 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
* @returns {void}
*/
function performCheck(leftNode, rightNode, reportNode) {
- if (rightNode.type !== "MemberExpression" || rightNode.object.type === "Super") {
+ if (
+ rightNode.type !== "MemberExpression" ||
+ rightNode.object.type === "Super" ||
+ rightNode.property.type === "PrivateIdentifier"
+ ) {
return;
}
docs: {
description: "disallow the use of `Math.pow` in favor of the `**` operator",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/prefer-exponentiation-operator"
},
docs: {
description: "enforce using named capture group in regular expression",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/prefer-named-capture-group"
},
docs: {
description: "disallow `parseInt()` and `Number.parseInt()` in favor of binary, octal, and hexadecimal literals",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/prefer-numeric-literals"
},
docs: {
description:
"disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead.",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/prefer-object-spread"
},
docs: {
description: "require using Error objects as Promise rejection reasons",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/prefer-promise-reject-errors"
},
docs: {
description: "require `Reflect` methods where applicable",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/prefer-reflect"
},
docs: {
description: "disallow use of the `RegExp` constructor in favor of regular expression literals",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/prefer-regex-literals"
},
docs: {
description: "require rest parameters instead of `arguments`",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/prefer-rest-params"
},
docs: {
description: "require spread operators instead of `.apply()`",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/prefer-spread"
},
docs: {
description: "require template literals instead of string concatenation",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/prefer-template"
},
docs: {
description: "require quotes around object literal property names",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/quote-props"
},
/**
* Checks whether a certain string constitutes an ES3 token
- * @param {string} tokenStr The string to be checked.
+ * @param {string} tokenStr The string to be checked.
* @returns {boolean} `true` if it is an ES3 token.
*/
function isKeyword(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
+ * @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
*/
/**
* Ensures that a property's key is quoted only when necessary
- * @param {ASTNode} node Property AST node
+ * @param {ASTNode} node Property AST node
* @returns {void}
*/
function checkUnnecessaryQuotes(node) {
/**
* Ensures that a property's key is quoted
- * @param {ASTNode} node Property AST node
+ * @param {ASTNode} node Property AST node
* @returns {void}
*/
function checkOmittedQuotes(node) {
/**
* 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
+ * @param {ASTNode} node Property AST node
+ * @param {boolean} checkQuotesRedundancy Whether to check quotes' redundancy
* @returns {void}
*/
function checkConsistency(node, checkQuotesRedundancy) {
docs: {
description: "enforce the consistent use of either backticks, double, or single quotes",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/quotes"
},
// LiteralPropertyName.
case "Property":
+ case "PropertyDefinition":
case "MethodDefinition":
return parent.key === node && !parent.computed;
docs: {
description: "enforce the consistent use of the radix argument when using `parseInt()`",
- category: "Best Practices",
recommended: false,
- url: "https://eslint.org/docs/rules/radix",
- suggestion: true
+ url: "https://eslint.org/docs/rules/radix"
},
+ hasSuggestions: true,
+
schema: [
{
enum: ["always", "as-needed"]
reference.from.variableScope === functionScope);
}
+/**
+ * Represents segment information.
+ */
class SegmentInfo {
constructor() {
this.info = new WeakMap();
docs: {
description: "disallow assignments that can lead to race conditions due to usage of `await` or `yield`",
- category: "Possible Errors",
recommended: false,
url: "https://eslint.org/docs/rules/require-atomic-updates"
},
fixable: null,
- schema: [],
+
+ schema: [{
+ type: "object",
+ properties: {
+ allowProperties: {
+ type: "boolean",
+ default: false
+ }
+ },
+ additionalProperties: false
+ }],
messages: {
- nonAtomicUpdate: "Possible race condition: `{{value}}` might be reassigned based on an outdated value of `{{value}}`."
+ 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[0] && context.options[0].allowProperties;
+
const sourceCode = context.getSourceCode();
const assignmentReferences = new Map();
const segmentInfo = new SegmentInfo();
const variable = reference.resolved;
if (segmentInfo.isOutdated(codePath.currentSegments, variable)) {
- context.report({
- node: node.parent,
- messageId: "nonAtomicUpdate",
- data: {
- value: sourceCode.getText(node.parent.left)
- }
- });
+ 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
+ }
+ });
+ }
+
}
}
}
docs: {
description: "disallow async functions which have no `await` expression",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/require-await"
},
/**
* @fileoverview Rule to check for jsdoc presence.
* @author Gyandeep Singh
+ * @deprecated in ESLint v5.10.0
*/
"use strict";
docs: {
description: "require JSDoc comments",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/require-jsdoc"
},
docs: {
description: "enforce the use of `u` flag on RegExp",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/require-unicode-regexp"
},
docs: {
description: "require generator functions to contain `yield`",
- category: "ECMAScript 6",
recommended: true,
url: "https://eslint.org/docs/rules/require-yield"
},
docs: {
description: "enforce spacing between rest and spread operators and their expressions",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/rest-spread-spacing"
},
docs: {
description: "enforce consistent spacing before and after semicolons",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/semi-spacing"
},
if (node.test) {
checkSemicolonSpacing(sourceCode.getTokenAfter(node.test), node);
}
- }
+ },
+ PropertyDefinition: checkNode
};
}
};
// Rule Definition
//------------------------------------------------------------------------------
-const SELECTOR = `:matches(${
- [
- "BreakStatement", "ContinueStatement", "DebuggerStatement",
- "DoWhileStatement", "ExportAllDeclaration",
- "ExportDefaultDeclaration", "ExportNamedDeclaration",
- "ExpressionStatement", "ImportDeclaration", "ReturnStatement",
- "ThrowStatement", "VariableDeclaration"
- ].join(",")
-})`;
+const SELECTOR = [
+ "BreakStatement", "ContinueStatement", "DebuggerStatement",
+ "DoWhileStatement", "ExportAllDeclaration",
+ "ExportDefaultDeclaration", "ExportNamedDeclaration",
+ "ExpressionStatement", "ImportDeclaration", "ReturnStatement",
+ "ThrowStatement", "VariableDeclaration", "PropertyDefinition"
+].join(",");
/**
* Get the child node list of a given node.
- * This returns `Program#body`, `BlockStatement#body`, or `SwitchCase#consequent`.
+ * This returns `BlockStatement#body`, `StaticBlock#body`, `Program#body`,
+ * `ClassBody#body`, or `SwitchCase#consequent`.
* This is used to check whether a node is the first/last child.
* @param {Node} node A node to get child node list.
* @returns {Node[]|null} The child node list.
function getChildren(node) {
const t = node.type;
- if (t === "BlockStatement" || t === "Program") {
+ if (
+ t === "BlockStatement" ||
+ t === "StaticBlock" ||
+ t === "Program" ||
+ t === "ClassBody"
+ ) {
return node.body;
}
if (t === "SwitchCase") {
docs: {
description: "enforce location of semicolons",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/semi-style"
},
docs: {
description: "require or disallow semicolons instead of ASI",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/semi"
},
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);
);
}
+ /**
+ * 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.
if (isRedundantSemi(sourceCode.getLastToken(node))) {
return true; // `;;` or `;}`
}
+ if (maybeClassFieldAsiHazard(node)) {
+ return false;
+ }
if (isOnSameLineWithNextToken(node)) {
return false; // One liner.
}
- if (beforeStatementContinuationChars === "never" && !maybeAsiHazardAfter(node)) {
+
+ // 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))) {
}
/**
- * Checks a node to see if it's in a one-liner block statement.
+ * 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 in a one-liner block statement.
+ * @returns {boolean} whether the node is the last item in a one-liner block.
*/
- function isOneLinerBlock(node) {
+ function isLastInOneLinerBlock(node) {
const parent = node.parent;
const nextToken = sourceCode.getTokenAfter(node);
if (!nextToken || nextToken.value !== "}") {
return false;
}
- return (
- !!parent &&
- parent.type === "BlockStatement" &&
- parent.loc.start.line === parent.loc.end.line
- );
+
+ 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;
}
/**
if (never) {
if (isSemi && canRemoveSemicolon(node)) {
report(node, true);
- } else if (!isSemi && beforeStatementContinuationChars === "always" && maybeAsiHazardBefore(sourceCode.getTokenAfter(node))) {
+ } else if (
+ !isSemi && beforeStatementContinuationChars === "always" &&
+ node.type !== "PropertyDefinition" &&
+ maybeAsiHazardBefore(sourceCode.getTokenAfter(node))
+ ) {
report(node);
}
} else {
- const oneLinerBlock = (exceptOneLine && isOneLinerBlock(node));
+ const oneLinerBlock = (exceptOneLine && isLastInOneLinerBlock(node));
if (isSemi && oneLinerBlock) {
report(node, true);
if (!/(?:Class|Function)Declaration/u.test(node.declaration.type)) {
checkForSemicolon(node);
}
- }
+ },
+ PropertyDefinition: checkForSemicolon
};
}
docs: {
description: "enforce sorted import declarations within modules",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/sort-imports"
},
docs: {
description: "require object keys to be sorted",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/sort-keys"
},
docs: {
description: "require variables within the same declaration block to be sorted",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/sort-vars"
},
docs: {
description: "enforce consistent spacing before blocks",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/space-before-blocks"
},
* 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);
+ return (
+ astUtils.isArrowToken(precedingToken) ||
+ (
+ astUtils.isKeywordToken(precedingToken) &&
+ !isFunctionBody(node)
+ ) ||
+ (
+ astUtils.isColonToken(precedingToken) &&
+ node.parent &&
+ node.parent.type === "SwitchCase" &&
+ precedingToken === astUtils.getSwitchCaseColonToken(node.parent, sourceCode)
+ )
+ );
}
/**
docs: {
description: "enforce consistent spacing before `function` definition opening parenthesis",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/space-before-function-paren"
},
docs: {
description: "enforce consistent spacing inside parentheses",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/space-in-parens"
},
*/
"use strict";
+const { isEqToken } = require("./utils/ast-utils");
+
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
docs: {
description: "require spacing around infix operators",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/space-infix-ops"
},
BinaryExpression: checkBinary,
LogicalExpression: checkBinary,
ConditionalExpression: checkConditional,
- VariableDeclarator: checkVar
+ 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);
+ }
+ }
};
}
docs: {
description: "enforce consistent spacing before or after unary operators",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/space-unary-ops"
},
docs: {
description: "enforce consistent spacing after the `//` or `/*` in a comment",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/spaced-comment"
},
docs: {
description: "require or disallow strict mode directives",
- category: "Strict Mode",
recommended: false,
url: "https://eslint.org/docs/rules/strict"
},
docs: {
description: "enforce spacing around colons of switch statements",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/switch-colon-spacing"
},
const beforeSpacing = options.before === true; // false by default
const afterSpacing = options.after !== false; // true by default
- /**
- * Get the colon token of the given SwitchCase node.
- * @param {ASTNode} node The SwitchCase node to get.
- * @returns {Token} The colon token of the node.
- */
- function getColonToken(node) {
- if (node.test) {
- return sourceCode.getTokenAfter(node.test, astUtils.isColonToken);
- }
- return sourceCode.getFirstToken(node, 1);
- }
-
/**
* Check whether the spacing between the given 2 tokens is valid or not.
* @param {Token} left The left token to check.
return {
SwitchCase(node) {
- const colonToken = getColonToken(node);
+ const colonToken = astUtils.getSwitchCaseColonToken(node, sourceCode);
const beforeToken = sourceCode.getTokenBefore(colonToken);
const afterToken = sourceCode.getTokenAfter(colonToken);
docs: {
description: "require symbol descriptions",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/symbol-description"
},
docs: {
description: "require or disallow spacing around embedded expressions of template strings",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/template-curly-spacing"
},
docs: {
description: "require or disallow spacing between template tags and their literals",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/template-tag-spacing"
},
docs: {
description: "require or disallow Unicode byte order mark (BOM)",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/unicode-bom"
},
* @returns {boolean} `true` if the node is 'NaN' identifier.
*/
function isNaNIdentifier(node) {
- return Boolean(node) && node.type === "Identifier" && node.name === "NaN";
+ return Boolean(node) && (
+ astUtils.isSpecificId(node, "NaN") ||
+ astUtils.isSpecificMemberAccess(node, "Number", "NaN")
+ );
}
//------------------------------------------------------------------------------
docs: {
description: "require calls to `isNaN()` when checking for `NaN`",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/use-isnan"
},
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", "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;
return getStaticPropertyName(node.expression);
case "Property":
+ case "PropertyDefinition":
case "MethodDefinition":
prop = node.key;
break;
return true;
case "Identifier":
+ case "PrivateIdentifier":
return left.name === right.name;
case "Literal":
return equalLiteralValue(left, right);
nextToken.value === ")" && nextToken.range[0] >= node.range[1];
}
+/**
+ * Checks if the given token is a `=` token or not.
+ * @param {Token} token The token to check.
+ * @returns {boolean} `true` if the token is a `=` token.
+ */
+function isEqToken(token) {
+ return token.value === "=" && token.type === "Punctuator";
+}
+
/**
* Checks if the given token is an arrow token or not.
* @param {Token} token The token to check.
* @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);
return LOGICAL_ASSIGNMENT_OPERATORS.has(operator);
}
+/**
+ * Get the colon token of the given SwitchCase node.
+ * @param {ASTNode} node The SwitchCase node to get.
+ * @param {SourceCode} sourceCode The source code object to get tokens.
+ * @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);
+}
+
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
isOpeningBracketToken,
isOpeningParenToken,
isSemicolonToken,
+ isEqToken,
/**
* Checks whether or not a given node is a string literal.
/**
* 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.
+ * @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
*/
*
* 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.
* - 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.
+ * @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)
* 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;
* 5e1_000 // false
* 5n // false
* 1_000n // false
- * '5' // false
+ * "5" // false
+ *
*/
isDecimalInteger(node) {
return node.type === "Literal" && typeof node.value === "number" &&
* - `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.
*/
const parent = node.parent;
const tokens = [];
- if (parent.type === "MethodDefinition" && parent.static) {
- tokens.push("static");
+ 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");
tokens.push("generator");
}
- if (node.type === "ArrowFunctionExpression") {
- tokens.push("arrow", "function");
- } else if (parent.type === "Property" || parent.type === "MethodDefinition") {
+ if (parent.type === "Property" || parent.type === "MethodDefinition") {
if (parent.kind === "constructor") {
return "constructor";
}
} else {
tokens.push("method");
}
+ } else if (parent.type === "PropertyDefinition") {
+ tokens.push("method");
} else {
+ if (node.type === "ArrowFunctionExpression") {
+ tokens.push("arrow");
+ }
tokens.push("function");
}
- if (node.id) {
- tokens.push(`'${node.id.name}'`);
- } else {
- const name = getStaticPropertyName(parent);
+ 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}'`);
+ 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(" ");
* ^^^^^^^^^^^^^^
* - `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.
let start = null;
let end = null;
- if (node.type === "ArrowFunctionExpression") {
+ 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 if (parent.type === "Property" || parent.type === "MethodDefinition") {
- start = parent.loc.start;
- end = getOpeningParenOfParams(node, sourceCode).loc.start;
} else {
start = node.loc.start;
end = getOpeningParenOfParams(node, sourceCode).loc.start;
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
+ * @param {ASTNode} node ASTNode to check
* @returns {boolean} True if there is a chance it contains an Error obj
*/
couldBeError(node) {
return true;
}
+ if (rightToken.type === "PrivateIdentifier") {
+ return true;
+ }
+
return false;
},
isSpecificMemberAccess,
equalLiteralValue,
isSameReference,
- isLogicalAssignmentOperator
+ isLogicalAssignmentOperator,
+ getSwitchCaseColonToken
};
* const rules = new LazyLoadingRuleMap([
* ["eqeqeq", () => require("eqeqeq")],
* ["semi", () => require("semi")],
- * ["no-unused-vars", () => require("no-unused-vars")],
- * ])
+ * ["no-unused-vars", () => require("no-unused-vars")]
+ * ]);
*
- * rules.get("semi") // call `() => require("semi")` here.
+ * rules.get("semi"); // call `() => require("semi")` here.
*
* @extends {Map<string, () => Rule>}
*/
/**
* @fileoverview Validates JSDoc comments are syntactically correct
* @author Nicholas C. Zakas
+ * @deprecated in ESLint v5.10.0
*/
"use strict";
docs: {
description: "enforce valid JSDoc comments",
- category: "Possible Errors",
recommended: false,
url: "https://eslint.org/docs/rules/valid-jsdoc"
},
docs: {
description: "enforce comparing `typeof` expressions against valid strings",
- category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/valid-typeof"
},
docs: {
description: "require `var` declarations be placed at the top of their containing scope",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/vars-on-top"
},
// Helpers
//--------------------------------------------------------------------------
- // eslint-disable-next-line jsdoc/require-description
/**
+ * Has AST suggesting a directive.
* @param {ASTNode} node any node
* @returns {boolean} whether the given node structurally represents a directive
*/
const l = statements.length;
let i = 0;
- // skip over directives
- for (; i < l; ++i) {
- if (!looksLikeDirective(statements[i]) && !looksLikeImport(statements[i])) {
- break;
+ // 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;
+ }
}
}
/**
* Checks whether variable is on top at functional block scope level
* @param {ASTNode} node The node to check
- * @param {ASTNode} parent Parent of the node
- * @param {ASTNode} grandParent Parent of the node's parent
* @returns {void}
*/
- function blockScopeVarCheck(node, parent, grandParent) {
- if (!(/Function/u.test(grandParent.type) &&
- parent.type === "BlockStatement" &&
- isVarOnTop(node, parent.body))) {
- context.report({ node, messageId: "top" });
+ 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" });
}
//--------------------------------------------------------------------------
} else if (node.parent.type === "Program") {
globalVarCheck(node, node.parent);
} else {
- blockScopeVarCheck(node, node.parent, node.parent.parent);
+ blockScopeVarCheck(node);
}
}
};
docs: {
description: "require parentheses around immediate `function` invocations",
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/wrap-iife"
},
docs: {
description: "require parenthesis around regex literals",
- category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/wrap-regex"
},
docs: {
description: "require or disallow spacing around the `*` in `yield*` expressions",
- category: "ECMAScript 6",
recommended: false,
url: "https://eslint.org/docs/rules/yield-star-spacing"
},
docs: {
description: 'require or disallow "Yoda" conditions',
- category: "Best Practices",
recommended: false,
url: "https://eslint.org/docs/rules/yoda"
},
});
ajv.addMetaSchema(metaSchema);
- // eslint-disable-next-line no-underscore-dangle
+ // eslint-disable-next-line no-underscore-dangle -- Ajv's API
ajv._opts.defaultMeta = metaSchema.id;
return ajv;
const
util = require("util"),
configSchema = require("../../conf/config-schema"),
- BuiltInEnvironments = require("@eslint/eslintrc/conf/environments"),
BuiltInRules = require("../rules"),
- ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"),
+ {
+ Legacy: {
+ ConfigOps,
+ environments: BuiltInEnvironments
+ }
+ } = require("@eslint/eslintrc"),
{ emitDeprecationWarning } = require("./deprecation-warnings");
const ajv = require("./ajv")();
/**
* Validates a rule's severity and returns the severity value. Throws an error if the severity is invalid.
* @param {options} options The given options for the rule.
+ * @throws {Error} Wrong severity value.
* @returns {number|string} The rule's severity value
*/
function validateRuleSeverity(options) {
* Validates the non-severity options passed to a rule, based on its schema.
* @param {{create: Function}} rule The rule to validate
* @param {Array} localOptions The options for the rule, excluding severity
+ * @throws {Error} Any rule validation errors.
* @returns {void}
*/
function validateRuleSchema(rule, localOptions) {
* @param {Array|number} options The given options for the rule.
* @param {string|null} source The name of the configuration source to report in any errors. If null or undefined,
* no source is prepended to the message.
+ * @throws {Error} Upon any bad rule configuration.
* @returns {void}
*/
function validateRuleOptions(rule, ruleId, options, source = null) {
* Validates an environment object
* @param {Object} environment The environment config object to validate.
* @param {string} source The name of the configuration source to report in any errors.
- * @param {function(envId:string): Object} [getAdditionalEnv] A map from strings to loaded environments.
+ * @param {(envId:string) => Object} [getAdditionalEnv] A map from strings to loaded environments.
* @returns {void}
*/
function validateEnvironment(
* Validates a rules config object
* @param {Object} rulesConfig The rules config object to validate.
* @param {string} source The name of the configuration source to report in any errors.
- * @param {function(ruleId:string): Object} getAdditionalRule A map from strings to loaded rules
+ * @param {(ruleId:string) => Object} getAdditionalRule A map from strings to loaded rules
* @returns {void}
*/
function validateRules(
* Validate `processor` configuration.
* @param {string|undefined} processorName The processor name.
* @param {string} source The name of config file.
- * @param {function(id:string): Processor} getProcessor The getter of defined processors.
+ * @param {(id:string) => Processor} getProcessor The getter of defined processors.
+ * @throws {Error} For invalid processor configuration.
* @returns {void}
*/
function validateProcessor(processorName, source, getProcessor) {
* Validates the top level properties of the config object.
* @param {Object} config The config object to validate.
* @param {string} source The name of the configuration source to report in any errors.
+ * @throws {Error} For any config invalid per the schema.
* @returns {void}
*/
function validateConfigSchema(config, source = null) {
* Validates an entire config object.
* @param {Object} config The config object to validate.
* @param {string} source The name of the configuration source to report in any errors.
- * @param {function(ruleId:string): Object} [getAdditionalRule] A map from strings to loaded rules.
- * @param {function(envId:string): Object} [getAdditionalEnv] A map from strings to loaded envs.
+ * @param {(ruleId:string) => Object} [getAdditionalRule] A map from strings to loaded rules.
+ * @param {(envId:string) => Object} [getAdditionalEnv] A map from strings to loaded envs.
* @returns {void}
*/
function validate(config, source, getAdditionalRule, getAdditionalEnv) {
"use strict";
-/* eslint no-console: "off" */
+/* eslint no-console: "off" -- Logging util */
/* istanbul ignore next */
module.exports = {
"use strict";
-const Module = require("module");
-
-/*
- * `Module.createRequire` is added in v12.2.0. It supports URL as well.
- * We only support the case where the argument is a filepath, not a URL.
- */
-// eslint-disable-next-line node/no-unsupported-features/node-builtins, node/no-deprecated-api
-const createRequire = Module.createRequire || Module.createRequireFromPath;
+const { createRequire } = require("module");
module.exports = {
* @param {string} moduleName The name of a Node module, or a path to a Node module.
* @param {string} relativeToPath An absolute path indicating the module that `moduleName` should be resolved relative to. This must be
* a file rather than a directory, but the file need not actually exist.
+ * @throws {Error} Any error from `module.createRequire` or its `resolve`.
* @returns {string} The absolute path that would result from calling `require.resolve(moduleName)` in a file located at `relativeToPath`
*/
resolve(moduleName, relativeToPath) {
* 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) {
/**
* 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) {
* 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 } = {}) {
this._leave = null;
}
- // eslint-disable-next-line jsdoc/require-description
/**
+ * Gives current node.
* @returns {ASTNode} The current node.
*/
current() {
return this._current;
}
- // eslint-disable-next-line jsdoc/require-description
/**
+ * Gives a a copy of the ancestor nodes.
* @returns {ASTNode[]} The ancestor nodes.
*/
parents() {
/**
* @typedef {Object} ParserOptions
* @property {EcmaFeatures} [ecmaFeatures] The optional features.
- * @property {3|5|6|7|8|9|10|11|12|2015|2016|2017|2018|2019|2020|2021} [ecmaVersion] The ECMAScript version (or revision number).
+ * @property {3|5|6|7|8|9|10|11|12|13|2015|2016|2017|2018|2019|2020|2021|2022} [ecmaVersion] The ECMAScript version (or revision number).
* @property {"script"|"module"} [sourceType] The source code type.
*/
/**
* @typedef {Object} LintMessage
- * @property {number} column The 1-based column number.
+ * @property {number|undefined} column The 1-based column number.
* @property {number} [endColumn] The 1-based column number of the end location.
* @property {number} [endLine] The 1-based line number of the end location.
* @property {boolean} fatal If `true` then this is a fatal error.
* @property {{range:[number,number], text:string}} [fix] Information for autofix.
- * @property {number} line The 1-based line number.
+ * @property {number|undefined} line The 1-based line number.
* @property {string} message The error message.
* @property {string|null} ruleId The ID of the rule which makes this message.
* @property {0|1|2} severity The severity of this message.
// Public Interface
//------------------------------------------------------------------------------
+/**
+ * Represents parsed source code.
+ */
class SourceCode extends TokenStore {
/**
- * Represents parsed source code.
* @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.
/**
* The flag to indicate that the source code has Unicode BOM.
- * @type boolean
+ * @type {boolean}
*/
this.hasBOM = (text.charCodeAt(0) === 0xFEFF);
/**
* The original text source code.
* BOM was stripped from this text.
- * @type string
+ * @type {string}
*/
this.text = (this.hasBOM ? text.slice(1) : text);
/**
* The parsed AST for the source code.
- * @type ASTNode
+ * @type {ASTNode}
*/
this.ast = ast;
/**
* 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[]
+ * @type {string[]}
*/
this.lines = [];
this.lineStartIndices = [0];
let currentToken = this.getTokenBefore(node, { includeComments: true });
while (currentToken && isCommentToken(currentToken)) {
- if (node.parent && (currentToken.start < node.parent.start)) {
+ if (node.parent && node.parent.type !== "Program" && (currentToken.start < node.parent.start)) {
break;
}
comments.leading.push(currentToken);
currentToken = this.getTokenAfter(node, { includeComments: true });
while (currentToken && isCommentToken(currentToken)) {
- if (node.parent && (currentToken.end > node.parent.end)) {
+ if (node.parent && node.parent.type !== "Program" && (currentToken.end > node.parent.end)) {
break;
}
comments.trailing.push(currentToken);
/**
* 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
*/
* @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
*/
* @abstract
*/
/* istanbul ignore next */
- moveNext() { // eslint-disable-line class-methods-use-this
+ moveNext() { // eslint-disable-line class-methods-use-this -- Unused
throw new Error("Not implemented.");
}
};
--- /dev/null
+/**
+ * @fileoverview APIs that are not officially supported by ESLint.
+ * These APIs may change or be removed at any time. Use at your
+ * own risk.
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//-----------------------------------------------------------------------------
+// Requirements
+//-----------------------------------------------------------------------------
+
+const { FileEnumerator } = require("./cli-engine/file-enumerator");
+
+//-----------------------------------------------------------------------------
+// Exports
+//-----------------------------------------------------------------------------
+
+module.exports = {
+ builtinRules: require("./rules"),
+ FileEnumerator
+};
{
"name": "eslint",
- "version": "7.28.0",
+ "version": "8.3.0",
"author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
"description": "An AST-based pattern checker for JavaScript.",
"bin": {
"eslint": "./bin/eslint.js"
},
"main": "./lib/api.js",
+ "exports": {
+ "./package.json": "./package.json",
+ ".": "./lib/api.js",
+ "./use-at-your-own-risk": "./lib/unsupported-api.js"
+ },
"scripts": {
"test": "node Makefile.js test",
"test:cli": "mocha",
"generate-betarelease": "node Makefile.js generatePrerelease -- beta",
"generate-rcrelease": "node Makefile.js generatePrerelease -- rc",
"publish-release": "node Makefile.js publishRelease",
- "docs": "node Makefile.js docs",
"gensite": "node Makefile.js gensite",
"webpack": "node Makefile.js webpack",
"perf": "node Makefile.js perf"
"homepage": "https://eslint.org",
"bugs": "https://github.com/eslint/eslint/issues/",
"dependencies": {
- "@babel/code-frame": "7.12.11",
- "@eslint/eslintrc": "^0.4.2",
+ "@eslint/eslintrc": "^1.0.4",
+ "@humanwhocodes/config-array": "^0.6.0",
"ajv": "^6.10.0",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
- "debug": "^4.0.1",
+ "debug": "^4.3.2",
"doctrine": "^3.0.0",
"enquirer": "^2.3.5",
"escape-string-regexp": "^4.0.0",
- "eslint-scope": "^5.1.1",
- "eslint-utils": "^2.1.0",
- "eslint-visitor-keys": "^2.0.0",
- "espree": "^7.3.1",
+ "eslint-scope": "^7.1.0",
+ "eslint-utils": "^3.0.0",
+ "eslint-visitor-keys": "^3.1.0",
+ "espree": "^9.1.0",
"esquery": "^1.4.0",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
"file-entry-cache": "^6.0.1",
"functional-red-black-tree": "^1.0.1",
- "glob-parent": "^5.1.2",
+ "glob-parent": "^6.0.1",
"globals": "^13.6.0",
"ignore": "^4.0.6",
"import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
- "js-yaml": "^3.13.1",
+ "js-yaml": "^4.1.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1",
"lodash.merge": "^4.6.2",
"natural-compare": "^1.4.0",
"optionator": "^0.9.1",
"progress": "^2.0.0",
- "regexpp": "^3.1.0",
+ "regexpp": "^3.2.0",
"semver": "^7.2.1",
- "strip-ansi": "^6.0.0",
+ "strip-ansi": "^6.0.1",
"strip-json-comments": "^3.1.0",
- "table": "^6.0.9",
"text-table": "^0.2.0",
"v8-compile-cache": "^2.0.3"
},
"cheerio": "^0.22.0",
"common-tags": "^1.8.0",
"core-js": "^3.1.3",
- "dateformat": "^3.0.3",
+ "dateformat": "^4.5.1",
"ejs": "^3.0.2",
"eslint": "file:.",
"eslint-config-eslint": "file:packages/eslint-config-eslint",
- "eslint-plugin-eslint-plugin": "^3.0.3",
+ "eslint-plugin-eslint-comments": "^3.2.0",
+ "eslint-plugin-eslint-plugin": "^4.0.1",
"eslint-plugin-internal-rules": "file:tools/internal-rules",
- "eslint-plugin-jsdoc": "^25.4.3",
+ "eslint-plugin-jsdoc": "^37.0.0",
"eslint-plugin-node": "^11.1.0",
- "eslint-release": "^2.0.0",
+ "eslint-release": "^3.2.0",
"eslump": "^3.0.0",
"esprima": "^4.0.1",
- "fs-teardown": "^0.1.0",
+ "fs-teardown": "^0.1.3",
"glob": "^7.1.6",
"jsdoc": "^3.5.5",
"karma": "^6.1.1",
"karma-mocha": "^2.0.1",
"karma-mocha-reporter": "^2.2.5",
"karma-webpack": "^5.0.0",
- "lint-staged": "^10.1.2",
+ "lint-staged": "^11.0.0",
"load-perf": "^0.2.0",
- "markdownlint": "^0.19.0",
- "markdownlint-cli": "^0.22.0",
+ "markdownlint": "^0.23.1",
+ "markdownlint-cli": "^0.28.1",
"memfs": "^3.0.1",
"mocha": "^8.3.2",
"mocha-junit-reporter": "^2.0.0",
"npm-license": "^0.3.3",
"nyc": "^15.0.1",
"proxyquire": "^2.0.1",
- "puppeteer": "^7.1.0",
- "recast": "^0.19.0",
+ "puppeteer": "^9.1.1",
+ "recast": "^0.20.4",
"regenerator-runtime": "^0.13.2",
"shelljs": "^0.8.2",
- "sinon": "^9.0.1",
+ "sinon": "^11.0.0",
"temp": "^0.9.0",
"webpack": "^5.23.0",
"webpack-cli": "^4.5.0",
],
"license": "MIT",
"engines": {
- "node": "^10.12.0 || >=12.0.0"
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
}
- release
- <%- type %>
---
-# ESLint v<%= version %> released
We just pushed ESLint v<%- version %>, which is a <%- type %> release upgrade of ESLint. This release <% if (type !== "patch") { %>adds some new features and <% } %>fixes several bugs found in the previous release.<% if (type === "major") { %> This release also has some breaking changes, so please read the following closely.<% } %>
ESLint comes with several built-in formatters to control the appearance of the linting results, and supports third-party formatters as well.
-You can specify a formatter using the `--format` or `-f` flag on the command line. For example, `--format codeframe` uses the `codeframe` formatter.
+You can specify a formatter using the `--format` or `-f` flag on the command line. For example, `--format json` uses the `json` formatter.
The built-in formatter options are:
/**
- * @fileoverview Define classes what use the in-memory file system.
- *
- * This provides utilities to test `ConfigArrayFactory`,
- * `CascadingConfigArrayFactory`, `FileEnumerator`, `CLIEngine`, and `ESLint`.
- *
- * - `defineConfigArrayFactoryWithInMemoryFileSystem({ cwd, files })`
- * - `defineCascadingConfigArrayFactoryWithInMemoryFileSystem({ cwd, files })`
- * - `defineFileEnumeratorWithInMemoryFileSystem({ cwd, files })`
- * - `defineCLIEngineWithInMemoryFileSystem({ cwd, files })`
- * - `defineESLintWithInMemoryFileSystem({ cwd, files })`
- *
- * Those functions define correspond classes with the in-memory file system.
- * Those search config files, parsers, and plugins in the `files` option via the
- * in-memory file system.
- *
- * For each test case, it makes more readable if we define minimal files the
- * test case requires.
- *
- * For example:
- *
- * ```js
- * const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
- * files: {
- * "node_modules/eslint-config-foo/index.js": `
- * module.exports = {
- * parser: "./parser",
- * rules: {
- * "no-undef": "error"
- * }
- * }
- * `,
- * "node_modules/eslint-config-foo/parser.js": `
- * module.exports = {
- * parse() {}
- * }
- * `,
- * ".eslintrc.json": JSON.stringify({ root: true, extends: "foo" })
- * }
- * });
- * const factory = new ConfigArrayFactory();
- * const config = factory.loadFile(".eslintrc.json");
- *
- * assert(config[0].name === ".eslintrc.json » eslint-config-foo");
- * assert(config[0].filePath === path.resolve("node_modules/eslint-config-foo/index.js"));
- * assert(config[0].parser.filePath === path.resolve("node_modules/eslint-config-foo/parser.js"));
- *
- * assert(config[1].name === ".eslintrc.json");
- * assert(config[1].filePath === path.resolve(".eslintrc.json"));
- * assert(config[1].root === true);
- * ```
- *
+ * @fileoverview in-memory file system.
* @author Toru Nagashima <https://github.com/mysticatea>
*/
"use strict";
+//-----------------------------------------------------------------------------
+// Requirements
+//-----------------------------------------------------------------------------
+
const path = require("path");
-const vm = require("vm");
const { Volume, createFsFromVolume } = require("memfs");
-const Proxyquire = require("proxyquire/lib/proxyquire");
-
-const CascadingConfigArrayFactoryPath =
- require.resolve("@eslint/eslintrc/lib/cascading-config-array-factory");
-const CLIEnginePath =
- require.resolve("../../lib/cli-engine/cli-engine");
-const ConfigArrayFactoryPath =
- require.resolve("@eslint/eslintrc/lib/config-array-factory");
-const FileEnumeratorPath =
- require.resolve("../../lib/cli-engine/file-enumerator");
-const LoadRulesPath =
- require.resolve("../../lib/cli-engine/load-rules");
-const ESLintPath =
- require.resolve("../../lib/eslint/eslint");
-const ESLintAllPath =
- require.resolve("../../conf/eslint-all");
-const ESLintRecommendedPath =
- require.resolve("../../conf/eslint-recommended");
-
-// Ensure the needed files has been loaded and cached.
-require(CascadingConfigArrayFactoryPath);
-require(CLIEnginePath);
-require(ConfigArrayFactoryPath);
-require(FileEnumeratorPath);
-require(LoadRulesPath);
-require(ESLintPath);
-require("js-yaml");
-require("espree");
-
-// Override `_require` in order to throw runtime errors in stubs.
-const ERRORED = Symbol("errored");
-const proxyquire = new class extends Proxyquire {
- _require(...args) {
- const retv = super._require(...args); // eslint-disable-line no-underscore-dangle
-
- if (retv[ERRORED]) {
- throw retv[ERRORED];
- }
- return retv;
- }
-}(module).noCallThru().noPreserveCache();
-
-// Separated (sandbox) context to compile fixture files.
-const context = vm.createContext();
-
-/**
- * Check if a given path is an existing file.
- * @param {import("fs")} fs The file system.
- * @param {string} filePath Tha path to a file to check.
- * @returns {boolean} `true` if the file existed.
- */
-function isExistingFile(fs, filePath) {
- try {
- return fs.statSync(filePath).isFile();
- } catch {
- return false;
- }
-}
-
-/**
- * Get some paths to test.
- * @param {string} prefix The prefix to try.
- * @returns {string[]} The paths to test.
- */
-function getTestPaths(prefix) {
- return [
- path.join(prefix),
- path.join(`${prefix}.js`),
- path.join(prefix, "index.js")
- ];
-}
-
-/**
- * Iterate the candidate paths of a given request to resolve.
- * @param {string} request Tha package name or file path to resolve.
- * @param {string} relativeTo Tha path to the file what called this resolving.
- * @returns {IterableIterator<string>} The candidate paths.
- */
-function *iterateCandidatePaths(request, relativeTo) {
- if (path.isAbsolute(request)) {
- yield* getTestPaths(request);
- return;
- }
- if (/^\.{1,2}[/\\]/u.test(request)) {
- yield* getTestPaths(path.resolve(path.dirname(relativeTo), request));
- return;
- }
-
- let prevPath = path.resolve(relativeTo);
- let dirPath = path.dirname(prevPath);
-
- while (dirPath && dirPath !== prevPath) {
- yield* getTestPaths(path.join(dirPath, "node_modules", request));
- prevPath = dirPath;
- dirPath = path.dirname(dirPath);
- }
-}
-
-/**
- * Resolve a given module name or file path relatively in the given file system.
- * @param {import("fs")} fs The file system.
- * @param {string} request Tha package name or file path to resolve.
- * @param {string} relativeTo Tha path to the file what called this resolving.
- * @returns {void}
- */
-function fsResolve(fs, request, relativeTo) {
- for (const filePath of iterateCandidatePaths(request, relativeTo)) {
- if (isExistingFile(fs, filePath)) {
- return filePath;
- }
- }
- throw Object.assign(
- new Error(`Cannot find module '${request}'`),
- { code: "MODULE_NOT_FOUND" }
- );
-}
-
-/**
- * Compile a JavaScript file.
- * This is used to compile only fixture files, so this is minimam.
- * @param {import("fs")} fs The file system.
- * @param {Object} stubs The stubs.
- * @param {string} filePath The path to a JavaScript file to compile.
- * @param {string} content The source code to compile.
- * @returns {any} The exported value.
- */
-function compile(fs, stubs, filePath, content) {
- const code = `(function(exports, require, module, __filename, __dirname) { ${content} })`;
- const f = vm.runInContext(code, context);
- const exports = {};
- const module = { exports };
-
- f.call(
- exports,
- exports,
- request => {
- const modulePath = fsResolve(fs, request, filePath);
- const stub = stubs[modulePath];
-
- if (stub[ERRORED]) {
- throw stub[ERRORED];
- }
- return stub;
- },
- module,
- filePath,
- path.dirname(filePath)
- );
-
- return module.exports;
-}
-
-/**
- * Import a given file path in the given file system.
- * @param {import("fs")} fs The file system.
- * @param {Object} stubs The stubs.
- * @param {string} absolutePath Tha file path to import.
- * @returns {void}
- */
-function fsImportFresh(fs, stubs, absolutePath) {
- if (absolutePath === ESLintAllPath) {
- return require(ESLintAllPath);
- }
- if (absolutePath === ESLintRecommendedPath) {
- return require(ESLintRecommendedPath);
- }
-
- if (fs.existsSync(absolutePath)) {
- return compile(
- fs,
- stubs,
- absolutePath,
- fs.readFileSync(absolutePath, "utf8")
- );
- }
-
- throw Object.assign(
- new Error(`Cannot find module '${absolutePath}'`),
- { code: "MODULE_NOT_FOUND" }
- );
-}
+//-----------------------------------------------------------------------------
+// Helpers
+//-----------------------------------------------------------------------------
/**
* Define in-memory file system.
return fs;
}
-/**
- * Define stubbed `ConfigArrayFactory` class what uses the in-memory file system.
- * @param {Object} options The options.
- * @param {() => string} [options.cwd] The current working directory.
- * @param {Object} [options.files] The initial files definition in the in-memory file system.
- * @returns {{ fs: import("fs"), RelativeModuleResolver: import("../../lib/shared/relative-module-resolver"), ConfigArrayFactory: import("../../lib/cli-engine/config-array-factory")["ConfigArrayFactory"] }} The stubbed `ConfigArrayFactory` class.
- */
-function defineConfigArrayFactoryWithInMemoryFileSystem({
- cwd = process.cwd,
- files = {}
-} = {}) {
- const fs = defineInMemoryFs({ cwd, files });
- const RelativeModuleResolver = { resolve: fsResolve.bind(null, fs) };
-
- /*
- * Stubs for proxyquire.
- * This contains the JavaScript files in `options.files`.
- */
- const stubs = {};
-
- stubs.fs = fs;
- stubs["import-fresh"] = fsImportFresh.bind(null, fs, stubs);
- stubs["../shared/relative-module-resolver"] = RelativeModuleResolver;
-
- /*
- * Write all files to the in-memory file system and compile all JavaScript
- * files then set to `stubs`.
- */
- (function initFiles(directoryPath, definition) {
- for (const [filename, content] of Object.entries(definition)) {
- const filePath = path.resolve(directoryPath, filename);
-
- if (typeof content === "object") {
- initFiles(filePath, content);
- continue;
- }
-
- /*
- * Compile then stub if this file is a JavaScript file.
- * For parsers and plugins that `require()` will import.
- */
- if (path.extname(filePath) === ".js") {
- Object.defineProperty(stubs, filePath, {
- configurable: true,
- enumerable: true,
- get() {
- let stub;
-
- try {
- stub = compile(fs, stubs, filePath, content);
- } catch (error) {
- stub = { [ERRORED]: error };
- }
- Object.defineProperty(stubs, filePath, {
- configurable: true,
- enumerable: true,
- value: stub
- });
-
- return stub;
- }
- });
- }
- }
- }(cwd(), files));
-
- // Load the stubbed one.
- const { ConfigArrayFactory } = proxyquire(ConfigArrayFactoryPath, stubs);
-
- // Override the default cwd.
- return {
- fs,
- stubs,
- RelativeModuleResolver,
- ConfigArrayFactory: cwd === process.cwd
- ? ConfigArrayFactory
- : class extends ConfigArrayFactory {
- constructor(options) {
- super({ cwd: cwd(), ...options });
- }
- }
- };
-}
-
-/**
- * Define stubbed `CascadingConfigArrayFactory` class what uses the in-memory file system.
- * @param {Object} options The options.
- * @param {() => string} [options.cwd] The current working directory.
- * @param {Object} [options.files] The initial files definition in the in-memory file system.
- * @returns {{ fs: import("fs"), RelativeModuleResolver: import("../../../lib/shared/relative-module-resolver"), ConfigArrayFactory: import("../../../lib/cli-engine/config-array-factory")["ConfigArrayFactory"], CascadingConfigArrayFactory: import("../../../lib/cli-engine/cascading-config-array-factory")["CascadingConfigArrayFactory"] }} The stubbed `CascadingConfigArrayFactory` class.
- */
-function defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
- cwd = process.cwd,
- files = {}
-} = {}) {
- const { fs, stubs, RelativeModuleResolver, ConfigArrayFactory } =
- defineConfigArrayFactoryWithInMemoryFileSystem({ cwd, files });
- const loadRules = proxyquire(LoadRulesPath, stubs);
- const { CascadingConfigArrayFactory } =
- proxyquire(CascadingConfigArrayFactoryPath, {
- "./config-array-factory": { ConfigArrayFactory },
- "./load-rules": loadRules
- });
-
- // Override the default cwd.
- return {
- fs,
- RelativeModuleResolver,
- ConfigArrayFactory,
- CascadingConfigArrayFactory: cwd === process.cwd
- ? CascadingConfigArrayFactory
- : class extends CascadingConfigArrayFactory {
- constructor(options) {
- super({ cwd: cwd(), ...options });
- }
- }
- };
-}
-
-/**
- * Define stubbed `FileEnumerator` class what uses the in-memory file system.
- * @param {Object} options The options.
- * @param {() => string} [options.cwd] The current working directory.
- * @param {Object} [options.files] The initial files definition in the in-memory file system.
- * @returns {{ fs: import("fs"), RelativeModuleResolver: import("../../../lib/shared/relative-module-resolver"), ConfigArrayFactory: import("../../../lib/cli-engine/config-array-factory")["ConfigArrayFactory"], CascadingConfigArrayFactory: import("../../../lib/cli-engine/cascading-config-array-factory")["CascadingConfigArrayFactory"], FileEnumerator: import("../../../lib/cli-engine/file-enumerator")["FileEnumerator"] }} The stubbed `FileEnumerator` class.
- */
-function defineFileEnumeratorWithInMemoryFileSystem({
- cwd = process.cwd,
- files = {}
-} = {}) {
- const {
- fs,
- RelativeModuleResolver,
- ConfigArrayFactory,
- CascadingConfigArrayFactory
- } =
- defineCascadingConfigArrayFactoryWithInMemoryFileSystem({ cwd, files });
- const { FileEnumerator } = proxyquire(FileEnumeratorPath, {
- fs,
- "./cascading-config-array-factory": { CascadingConfigArrayFactory }
- });
-
- // Override the default cwd.
- return {
- fs,
- RelativeModuleResolver,
- ConfigArrayFactory,
- CascadingConfigArrayFactory,
- FileEnumerator: cwd === process.cwd
- ? FileEnumerator
- : class extends FileEnumerator {
- constructor(options) {
- super({ cwd: cwd(), ...options });
- }
- }
- };
-}
-
-/**
- * Define stubbed `CLIEngine` class what uses the in-memory file system.
- * @param {Object} options The options.
- * @param {() => string} [options.cwd] The current working directory.
- * @param {Object} [options.files] The initial files definition in the in-memory file system.
- * @returns {{ fs: import("fs"), RelativeModuleResolver: import("../../../lib/shared/relative-module-resolver"), ConfigArrayFactory: import("../../../lib/cli-engine/config-array-factory")["ConfigArrayFactory"], CascadingConfigArrayFactory: import("../../../lib/cli-engine/cascading-config-array-factory")["CascadingConfigArrayFactory"], FileEnumerator: import("../../../lib/cli-engine/file-enumerator")["FileEnumerator"], CLIEngine: import("../../../lib/cli-engine/cli-engine")["CLIEngine"], getCLIEngineInternalSlots: import("../../../lib/cli-engine/cli-engine")["getCLIEngineInternalSlots"] }} The stubbed `CLIEngine` class.
- */
-function defineCLIEngineWithInMemoryFileSystem({
- cwd = process.cwd,
- files = {}
-} = {}) {
- const {
- fs,
- RelativeModuleResolver,
- ConfigArrayFactory,
- CascadingConfigArrayFactory,
- FileEnumerator
- } =
- defineFileEnumeratorWithInMemoryFileSystem({ cwd, files });
- const { CLIEngine, getCLIEngineInternalSlots } = proxyquire(CLIEnginePath, {
- fs,
- "./cascading-config-array-factory": { CascadingConfigArrayFactory },
- "./file-enumerator": { FileEnumerator },
- "../shared/relative-module-resolver": RelativeModuleResolver
- });
-
- // Override the default cwd.
- return {
- fs,
- RelativeModuleResolver,
- ConfigArrayFactory,
- CascadingConfigArrayFactory,
- FileEnumerator,
- CLIEngine: cwd === process.cwd
- ? CLIEngine
- : class extends CLIEngine {
- constructor(options) {
- super({ cwd: cwd(), ...options });
- }
- },
- getCLIEngineInternalSlots
- };
-}
-
-/**
- * Define stubbed `ESLint` class that uses the in-memory file system.
- * @param {Object} options The options.
- * @param {() => string} [options.cwd] The current working directory.
- * @param {Object} [options.files] The initial files definition in the in-memory file system.
- * @returns {{ fs: import("fs"), RelativeModuleResolver: import("../../lib/shared/relative-module-resolver"), ConfigArrayFactory: import("../../lib/cli-engine/config-array-factory")["ConfigArrayFactory"], CascadingConfigArrayFactory: import("../../lib/cli-engine/cascading-config-array-factory")["CascadingConfigArrayFactory"], FileEnumerator: import("../../lib/cli-engine/file-enumerator")["FileEnumerator"], ESLint: import("../../lib/eslint/eslint")["ESLint"], getCLIEngineInternalSlots: import("../../lib//eslint/eslint")["getESLintInternalSlots"] }} The stubbed `ESLint` class.
- */
-function defineESLintWithInMemoryFileSystem({
- cwd = process.cwd,
- files = {}
-} = {}) {
- const {
- fs,
- RelativeModuleResolver,
- ConfigArrayFactory,
- CascadingConfigArrayFactory,
- FileEnumerator,
- CLIEngine,
- getCLIEngineInternalSlots
- } = defineCLIEngineWithInMemoryFileSystem({ cwd, files });
- const { ESLint, getESLintPrivateMembers } = proxyquire(ESLintPath, {
- "../cli-engine/cli-engine": { CLIEngine, getCLIEngineInternalSlots }
- });
-
- // Override the default cwd.
- return {
- fs,
- RelativeModuleResolver,
- ConfigArrayFactory,
- CascadingConfigArrayFactory,
- FileEnumerator,
- CLIEngine,
- getCLIEngineInternalSlots,
- ESLint: cwd === process.cwd
- ? ESLint
- : class extends ESLint {
- constructor(options) {
- super({ cwd: cwd(), ...options });
- }
- },
- getESLintPrivateMembers
- };
-}
+//-----------------------------------------------------------------------------
+// Exports
+//-----------------------------------------------------------------------------
module.exports = {
- defineInMemoryFs,
- defineConfigArrayFactoryWithInMemoryFileSystem,
- defineCascadingConfigArrayFactoryWithInMemoryFileSystem,
- defineFileEnumeratorWithInMemoryFileSystem,
- defineCLIEngineWithInMemoryFileSystem,
- defineESLintWithInMemoryFileSystem
+ defineInMemoryFs
};
//-----------------------------------------------------------------------------
const {
- defineInMemoryFs,
- defineConfigArrayFactoryWithInMemoryFileSystem,
- defineCascadingConfigArrayFactoryWithInMemoryFileSystem,
- defineFileEnumeratorWithInMemoryFileSystem,
- defineCLIEngineWithInMemoryFileSystem,
- defineESLintWithInMemoryFileSystem
+ defineInMemoryFs
} = require("./in-memory-fs");
const { createTeardown, addFile } = require("fs-teardown");
module.exports = {
unIndent,
defineInMemoryFs,
- defineConfigArrayFactoryWithInMemoryFileSystem,
- defineCascadingConfigArrayFactoryWithInMemoryFileSystem,
- defineFileEnumeratorWithInMemoryFileSystem,
- defineCLIEngineWithInMemoryFileSystem,
- defineESLintWithInMemoryFileSystem,
createCustomTeardown
};
"use strict";
+//-----------------------------------------------------------------------------
+// Requirements
+//-----------------------------------------------------------------------------
+
const childProcess = require("child_process");
const fs = require("fs");
const assert = require("chai").assert;
const path = require("path");
+//------------------------------------------------------------------------------
+// Data
+//------------------------------------------------------------------------------
+
const EXECUTABLE_PATH = path.resolve(path.join(__dirname, "../../bin/eslint.js"));
+//-----------------------------------------------------------------------------
+// Helpers
+//-----------------------------------------------------------------------------
+
/**
* Returns a Promise for when a child process exits
* @param {ChildProcess} exitingProcess The child process
* Asserts that the exit code of a given child process will equal the given value.
* @param {ChildProcess} exitingProcess The child process
* @param {number} expectedExitCode The expected exit code of the child process
- * @returns {Promise} A Promise that fulfills if the exit code ends up matching, and rejects otherwise.
+ * @returns {Promise<void>} A Promise that fulfills if the exit code ends up matching, and rejects otherwise.
*/
function assertExitCode(exitingProcess, expectedExitCode) {
return awaitExit(exitingProcess).then(exitCode => {
return awaitExit(runningProcess).then(() => ({ stdout, stderr }));
}
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
describe("bin/eslint.js", () => {
const forkedProcesses = new Set();
filePath: "<text>",
messages: [],
errorCount: 0,
+ fatalErrorCount: 0,
warningCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0,
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-eslintrc", "--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-eslintrc", "--rule", "semi:2"]);
const child = runESLint(["--no-ignore", invalidConfig]);
const exitCodeAssertion = assertExitCode(child, 2);
const outputAssertion = getOutput(child).then(output => {
- const expectedSubstring = ": bad indentation of a mapping entry at line";
-
assert.strictEqual(output.stdout, "");
- assert.include(output.stderr, expectedSubstring);
+ assert.match(
+ output.stderr,
+ /: bad indentation of a mapping entry \(\d+:\d+\)/u // a part of the error message from `js-yaml` dependency
+ );
});
return Promise.all([exitCodeAssertion, outputAssertion]);
--- /dev/null
+/*expected
+initial->s3_1->final;
+*/
+/*expected
+initial->s2_1->final;
+*/
+/*expected
+initial->s1_1->final;
+*/
+
+class Foo { a = () => b }
+
+/*DOT
+digraph {
+node[shape=box,style="rounded,filled",fillcolor=white];
+initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+s3_1[label="ArrowFunctionExpression:enter\nIdentifier (b)\nArrowFunctionExpression:exit"];
+initial->s3_1->final;
+}
+
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s2_1[label="ArrowFunctionExpression"];
+ initial->s2_1->final;
+}
+
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nPropertyDefinition:enter\nIdentifier (a)\nArrowFunctionExpression\nPropertyDefinition:exit\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"];
+ initial->s1_1->final;
+}
+*/
--- /dev/null
+/*expected
+initial->s2_1->final;
+*/
+/*expected
+initial->s1_1->final;
+*/
+
+class Foo { a = b() }
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s2_1[label="CallExpression:enter\nIdentifier (b)\nCallExpression:exit"];
+ initial->s2_1->final;
+}
+
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nPropertyDefinition:enter\nIdentifier (a)\nCallExpression\nPropertyDefinition:exit\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"];
+ initial->s1_1->final;
+}
+*/
--- /dev/null
+/*expected
+initial->s2_1->s2_2->s2_4;
+s2_1->s2_3->s2_4->final;
+*/
+/*expected
+initial->s1_1->final;
+*/
+
+
+class Foo { a = b ? c : d }
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s2_1[label="ConditionalExpression:enter\nIdentifier (b)"];
+ s2_2[label="Identifier (c)"];
+ s2_4[label="ConditionalExpression:exit"];
+ s2_3[label="Identifier (d)"];
+ initial->s2_1->s2_2->s2_4;
+ s2_1->s2_3->s2_4->final;
+}
+
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nPropertyDefinition:enter\nIdentifier (a)\nConditionalExpression\nPropertyDefinition:exit\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"];
+ initial->s1_1->final;
+}
+*/
--- /dev/null
+/*expected
+initial->s2_1->final;
+*/
+/*expected
+initial->s1_1->final;
+*/
+
+class Foo {
+ a = b;
+}
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s2_1[label="Identifier (b)"];
+ initial->s2_1->final;
+}
+
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nPropertyDefinition:enter\nIdentifier (a)\nIdentifier (b)\nPropertyDefinition:exit\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"];
+ initial->s1_1->final;
+}
+*/
--- /dev/null
+/*expected
+initial->s2_1->s2_2->s2_3;
+s2_1->s2_3->final;
+*/
+/*expected
+initial->s3_1->s3_2->s3_3->s3_4;
+s3_1->s3_4;
+s3_2->s3_4->final;
+*/
+/*expected
+initial->s4_1->s4_2->s4_3->s4_4->s4_5;
+s4_1->s4_5;
+s4_2->s4_5;
+s4_3->s4_5->final;
+*/
+/*expected
+initial->s1_1->final;
+*/
+class Foo { bar = a || b; static { x || y || z } baz = p || q || r || s; }
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s2_1[label="LogicalExpression:enter\nIdentifier (a)"];
+ s2_2[label="Identifier (b)"];
+ s2_3[label="LogicalExpression:exit"];
+ initial->s2_1->s2_2->s2_3;
+ s2_1->s2_3->final;
+}
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s3_1[label="StaticBlock:enter\nExpressionStatement:enter\nLogicalExpression:enter\nLogicalExpression:enter\nIdentifier (x)"];
+ s3_2[label="Identifier (y)\nLogicalExpression:exit"];
+ s3_3[label="Identifier (z)"];
+ s3_4[label="LogicalExpression:exit\nExpressionStatement:exit\nStaticBlock:exit"];
+ initial->s3_1->s3_2->s3_3->s3_4;
+ s3_1->s3_4;
+ s3_2->s3_4->final;
+}
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s4_1[label="LogicalExpression:enter\nLogicalExpression:enter\nLogicalExpression:enter\nIdentifier (p)"];
+ s4_2[label="Identifier (q)\nLogicalExpression:exit"];
+ s4_3[label="Identifier (r)\nLogicalExpression:exit"];
+ s4_4[label="Identifier (s)"];
+ s4_5[label="LogicalExpression:exit"];
+ initial->s4_1->s4_2->s4_3->s4_4->s4_5;
+ s4_1->s4_5;
+ s4_2->s4_5;
+ s4_3->s4_5->final;
+}
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nPropertyDefinition:enter\nIdentifier (bar)\nLogicalExpression\nPropertyDefinition:exit\nStaticBlock\nPropertyDefinition:enter\nIdentifier (baz)\nLogicalExpression\nPropertyDefinition:exit\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"];
+ initial->s1_1->final;
+}
+*/
--- /dev/null
+/*expected
+initial->s2_1->s2_2->s2_3;
+s2_1->s2_3->final;
+*/
+/*expected
+initial->s3_1->s3_2->s3_3->s3_4;
+s3_1->s3_4;
+s3_2->s3_4->final;
+*/
+/*expected
+initial->s4_1->s4_2->s4_3->s4_4->s4_5;
+s4_1->s4_5;
+s4_2->s4_5;
+s4_3->s4_5->final;
+*/
+/*expected
+initial->s1_1->final;
+*/
+class Foo { bar () { a || b } static { x || y || z } baz() { p || q || r || s } }
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s2_1[label="FunctionExpression:enter\nBlockStatement:enter\nExpressionStatement:enter\nLogicalExpression:enter\nIdentifier (a)"];
+ s2_2[label="Identifier (b)"];
+ s2_3[label="LogicalExpression:exit\nExpressionStatement:exit\nBlockStatement:exit\nFunctionExpression:exit"];
+ initial->s2_1->s2_2->s2_3;
+ s2_1->s2_3->final;
+}
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s3_1[label="StaticBlock:enter\nExpressionStatement:enter\nLogicalExpression:enter\nLogicalExpression:enter\nIdentifier (x)"];
+ s3_2[label="Identifier (y)\nLogicalExpression:exit"];
+ s3_3[label="Identifier (z)"];
+ s3_4[label="LogicalExpression:exit\nExpressionStatement:exit\nStaticBlock:exit"];
+ initial->s3_1->s3_2->s3_3->s3_4;
+ s3_1->s3_4;
+ s3_2->s3_4->final;
+}
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s4_1[label="FunctionExpression:enter\nBlockStatement:enter\nExpressionStatement:enter\nLogicalExpression:enter\nLogicalExpression:enter\nLogicalExpression:enter\nIdentifier (p)"];
+ s4_2[label="Identifier (q)\nLogicalExpression:exit"];
+ s4_3[label="Identifier (r)\nLogicalExpression:exit"];
+ s4_4[label="Identifier (s)"];
+ s4_5[label="LogicalExpression:exit\nExpressionStatement:exit\nBlockStatement:exit\nFunctionExpression:exit"];
+ initial->s4_1->s4_2->s4_3->s4_4->s4_5;
+ s4_1->s4_5;
+ s4_2->s4_5;
+ s4_3->s4_5->final;
+}
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nMethodDefinition:enter\nIdentifier (bar)\nFunctionExpression\nMethodDefinition:exit\nStaticBlock\nMethodDefinition:enter\nIdentifier (baz)\nFunctionExpression\nMethodDefinition:exit\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"];
+ initial->s1_1->final;
+}
+*/
--- /dev/null
+/*expected
+initial->s2_1->s2_2->s2_4;
+s2_1->s2_3->s2_4->final;
+*/
+/*expected
+initial->s1_1->final;
+*/
+class Foo { static { this.bar = a ? b : c; } }
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s2_1[label="StaticBlock:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nThisExpression\nIdentifier (bar)\nMemberExpression:exit\nConditionalExpression:enter\nIdentifier (a)"];
+ s2_2[label="Identifier (b)"];
+ s2_4[label="ConditionalExpression:exit\nAssignmentExpression:exit\nExpressionStatement:exit\nStaticBlock:exit"];
+ s2_3[label="Identifier (c)"];
+ initial->s2_1->s2_2->s2_4;
+ s2_1->s2_3->s2_4->final;
+}
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nStaticBlock\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"];
+ initial->s1_1->final;
+}
+*/
--- /dev/null
+/*expected
+initial->s2_1->final;
+*/
+/*expected
+initial->s1_1->final;
+*/
+class Foo { static {} }
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s2_1[label="StaticBlock"];
+ initial->s2_1->final;
+}
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nStaticBlock\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"];
+ initial->s1_1->final;
+}
+*/
--- /dev/null
+/*expected
+initial->s3_1->final;
+*/
+/*expected
+initial->s2_1->final;
+*/
+/*expected
+initial->s1_1->final;
+*/
+class Foo { static { (p) => {} } }
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s3_1[label="ArrowFunctionExpression:enter\nIdentifier (p)\nBlockStatement\nArrowFunctionExpression:exit"];
+ initial->s3_1->final;
+}
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s2_1[label="StaticBlock:enter\nExpressionStatement:enter\nArrowFunctionExpression\nExpressionStatement:exit\nStaticBlock:exit"];
+ initial->s2_1->final;
+}
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nStaticBlock\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"];
+ initial->s1_1->final;
+}
+*/
--- /dev/null
+/*expected
+initial->s2_1->s2_2->s2_4;
+s2_1->s2_3->s2_4->final;
+*/
+/*expected
+initial->s1_1->final;
+*/
+class Foo { static { if (bar) { this.baz = 1; } else { this.qux = 2; } } }
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s2_1[label="StaticBlock:enter\nIfStatement:enter\nIdentifier (bar)"];
+ s2_2[label="BlockStatement:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nThisExpression\nIdentifier (baz)\nMemberExpression:exit\nLiteral (1)\nAssignmentExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ s2_4[label="IfStatement:exit\nStaticBlock:exit"];
+ s2_3[label="BlockStatement:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nThisExpression\nIdentifier (qux)\nMemberExpression:exit\nLiteral (2)\nAssignmentExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+ initial->s2_1->s2_2->s2_4;
+ s2_1->s2_3->s2_4->final;
+}
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nStaticBlock\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"];
+ initial->s1_1->final;
+}
+*/
--- /dev/null
+/*expected
+initial->s2_1->final;
+*/
+/*expected
+initial->s3_1->final;
+*/
+/*expected
+initial->s1_1->final;
+*/
+class Foo { static { this.bar = 1; } static { this.baz = 2; } }
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s2_1[label="StaticBlock:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nThisExpression\nIdentifier (bar)\nMemberExpression:exit\nLiteral (1)\nAssignmentExpression:exit\nExpressionStatement:exit\nStaticBlock:exit"];
+ initial->s2_1->final;
+}
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s3_1[label="StaticBlock:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nThisExpression\nIdentifier (baz)\nMemberExpression:exit\nLiteral (2)\nAssignmentExpression:exit\nExpressionStatement:exit\nStaticBlock:exit"];
+ initial->s3_1->final;
+}
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nStaticBlock\nStaticBlock\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"];
+ initial->s1_1->final;
+}
+*/
--- /dev/null
+/*expected
+initial->s2_1->s2_2->s2_3->s2_4;
+s2_1->s2_4;
+s2_2->s2_4->final;
+*/
+/*expected
+initial->s3_1->s3_2->s3_3;
+s3_1->s3_3->final;
+*/
+/*expected
+initial->s1_1->final;
+*/
+class Foo { static { x || y || z } static { p || q } }
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s2_1[label="StaticBlock:enter\nExpressionStatement:enter\nLogicalExpression:enter\nLogicalExpression:enter\nIdentifier (x)"];
+ s2_2[label="Identifier (y)\nLogicalExpression:exit"];
+ s2_3[label="Identifier (z)"];
+ s2_4[label="LogicalExpression:exit\nExpressionStatement:exit\nStaticBlock:exit"];
+ initial->s2_1->s2_2->s2_3->s2_4;
+ s2_1->s2_4;
+ s2_2->s2_4->final;
+}
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s3_1[label="StaticBlock:enter\nExpressionStatement:enter\nLogicalExpression:enter\nIdentifier (p)"];
+ s3_2[label="Identifier (q)"];
+ s3_3[label="LogicalExpression:exit\nExpressionStatement:exit\nStaticBlock:exit"];
+ initial->s3_1->s3_2->s3_3;
+ s3_1->s3_3->final;
+}
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nStaticBlock\nStaticBlock\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"];
+ initial->s1_1->final;
+}
+*/
--- /dev/null
+/*expected
+initial->s2_1->final;
+*/
+/*expected
+initial->s1_1->final;
+*/
+class Foo { static { this.bar = baz; } }
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s2_1[label="StaticBlock:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nThisExpression\nIdentifier (bar)\nMemberExpression:exit\nIdentifier (baz)\nAssignmentExpression:exit\nExpressionStatement:exit\nStaticBlock:exit"];
+ initial->s2_1->final;
+}
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nStaticBlock\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"];
+ initial->s1_1->final;
+}
+*/
--- /dev/null
+/*expected
+initial->s2_1->s2_2->s2_4;
+s2_1->s2_3->s2_4->final;
+*/
+/*expected
+initial->s1_1->final;
+*/
+function Foo() { this.a = b ? c : d }; new Foo()
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s2_1[label="FunctionDeclaration:enter\nIdentifier (Foo)\nBlockStatement:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nThisExpression\nIdentifier (a)\nMemberExpression:exit\nConditionalExpression:enter\nIdentifier (b)"];
+ s2_2[label="Identifier (c)"];
+ s2_4[label="ConditionalExpression:exit\nAssignmentExpression:exit\nExpressionStatement:exit\nBlockStatement:exit\nFunctionDeclaration:exit"];
+ s2_3[label="Identifier (d)"];
+ initial->s2_1->s2_2->s2_4;
+ s2_1->s2_3->s2_4->final;
+}
+
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nFunctionDeclaration\nEmptyStatement\nExpressionStatement:enter\nNewExpression:enter\nIdentifier (Foo)\nNewExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ initial->s1_1->final;
+}
+*/
--- /dev/null
+/*expected
+initial->s1_1->s1_2->s1_4;
+s1_1->s1_3->s1_4->final;
+*/
+
+x = { a: b ? c : d }
+
+/*DOT
+digraph {
+ node[shape=box,style="rounded,filled",fillcolor=white];
+ initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+ s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (x)\nObjectExpression:enter\nProperty:enter\nIdentifier (a)\nConditionalExpression:enter\nIdentifier
+ (b)"];
+ s1_2[label="Identifier (c)"];
+ s1_4[label="ConditionalExpression:exit\nProperty:exit\nObjectExpression:exit\nAssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+ s1_3[label="Identifier (d)"];
+ initial->s1_1->s1_2->s1_4;
+ s1_1->s1_3->s1_4->final;
+}*/
--- /dev/null
+var foo 1
\ No newline at end of file
--- /dev/null
+/*eslint no-unused-vars: "error"*/
+
+var foo = 1;
\ No newline at end of file
--- /dev/null
+var foo = 1;
\ No newline at end of file
--- /dev/null
+"use strict";
+
+exports.parse = function (text, parserOptions) {
+ return {
+ "type": "Program",
+ "start": 0,
+ "end": 0,
+ "loc": {
+ "start": {
+ "line": 1,
+ "column": 0
+ },
+ "end": {
+ "line": 1,
+ "column": 0
+ }
+ },
+ "range": [
+ 0,
+ 0
+ ],
+ "body": [],
+ "sourceType": "script",
+ "comments": [],
+ "tokens": []
+ };
+};
"use strict";
const assert = require("assert");
-const ScopeManager = require("eslint-scope/lib/scope-manager");
-const Referencer = require("eslint-scope/lib/referencer");
+const { ScopeManager, Referencer } = require("eslint-scope");
const vk = require("eslint-visitor-keys");
class EnhancedReferencer extends Referencer {
--- /dev/null
+"use strict";
+
+/**
+ * Parser: @typescript-eslint/parser@5.0.0
+ *
+ * Source code:
+ * <Thing>this.blah
+ */
+
+exports.parse = () => ({
+ type: "Program",
+ body: [
+ {
+ type: "ExpressionStatement",
+ expression: {
+ type: "TSTypeAssertion",
+ typeAnnotation: {
+ type: "TSTypeReference",
+ typeName: {
+ type: "Identifier",
+ name: "Thing",
+ range: [1, 6],
+ loc: {
+ start: { line: 1, column: 1 },
+ end: { line: 1, column: 6 },
+ },
+ },
+ range: [1, 6],
+ loc: {
+ start: { line: 1, column: 1 },
+ end: { line: 1, column: 6 },
+ },
+ },
+ expression: {
+ type: "MemberExpression",
+ object: {
+ type: "ThisExpression",
+ range: [7, 11],
+ loc: {
+ start: { line: 1, column: 7 },
+ end: { line: 1, column: 11 },
+ },
+ },
+ property: {
+ type: "Identifier",
+ name: "blah",
+ range: [12, 16],
+ loc: {
+ start: { line: 1, column: 12 },
+ end: { line: 1, column: 16 },
+ },
+ },
+ computed: false,
+ optional: false,
+ range: [7, 16],
+ loc: {
+ start: { line: 1, column: 7 },
+ end: { line: 1, column: 16 },
+ },
+ },
+ range: [0, 16],
+ loc: {
+ start: { line: 1, column: 0 },
+ end: { line: 1, column: 16 },
+ },
+ },
+ range: [0, 16],
+ loc: {
+ start: { line: 1, column: 0 },
+ end: { line: 1, column: 16 },
+ },
+ },
+ ],
+ sourceType: "script",
+ range: [0, 16],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 16 } },
+ tokens: [
+ {
+ type: "Punctuator",
+ value: "<",
+ range: [0, 1],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } },
+ },
+ {
+ type: "Identifier",
+ value: "Thing",
+ range: [1, 6],
+ loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 6 } },
+ },
+ {
+ type: "Punctuator",
+ value: ">",
+ range: [6, 7],
+ loc: { start: { line: 1, column: 6 }, end: { line: 1, column: 7 } },
+ },
+ {
+ type: "Keyword",
+ value: "this",
+ range: [7, 11],
+ loc: {
+ start: { line: 1, column: 7 },
+ end: { line: 1, column: 11 },
+ },
+ },
+ {
+ type: "Punctuator",
+ value: ".",
+ range: [11, 12],
+ loc: {
+ start: { line: 1, column: 11 },
+ end: { line: 1, column: 12 },
+ },
+ },
+ {
+ type: "Identifier",
+ value: "blah",
+ range: [12, 16],
+ loc: {
+ start: { line: 1, column: 12 },
+ end: { line: 1, column: 16 },
+ },
+ },
+ ],
+ comments: [],
+});
--- /dev/null
+"use strict";
+
+/**
+ * Parser: @typescript-eslint/parser@5.0.0
+ *
+ * Source code:
+ * <Thing> this.blah
+ */
+
+exports.parse = () => ({
+ type: "Program",
+ body: [
+ {
+ type: "ExpressionStatement",
+ expression: {
+ type: "TSTypeAssertion",
+ typeAnnotation: {
+ type: "TSTypeReference",
+ typeName: {
+ type: "Identifier",
+ name: "Thing",
+ range: [1, 6],
+ loc: {
+ start: { line: 1, column: 1 },
+ end: { line: 1, column: 6 },
+ },
+ },
+ range: [1, 6],
+ loc: {
+ start: { line: 1, column: 1 },
+ end: { line: 1, column: 6 },
+ },
+ },
+ expression: {
+ type: "MemberExpression",
+ object: {
+ type: "ThisExpression",
+ range: [8, 12],
+ loc: {
+ start: { line: 1, column: 8 },
+ end: { line: 1, column: 12 },
+ },
+ },
+ property: {
+ type: "Identifier",
+ name: "blah",
+ range: [13, 17],
+ loc: {
+ start: { line: 1, column: 13 },
+ end: { line: 1, column: 17 },
+ },
+ },
+ computed: false,
+ optional: false,
+ range: [8, 17],
+ loc: {
+ start: { line: 1, column: 8 },
+ end: { line: 1, column: 17 },
+ },
+ },
+ range: [0, 17],
+ loc: {
+ start: { line: 1, column: 0 },
+ end: { line: 1, column: 17 },
+ },
+ },
+ range: [0, 17],
+ loc: {
+ start: { line: 1, column: 0 },
+ end: { line: 1, column: 17 },
+ },
+ },
+ ],
+ sourceType: "script",
+ range: [0, 17],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 17 } },
+ tokens: [
+ {
+ type: "Punctuator",
+ value: "<",
+ range: [0, 1],
+ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } },
+ },
+ {
+ type: "Identifier",
+ value: "Thing",
+ range: [1, 6],
+ loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 6 } },
+ },
+ {
+ type: "Punctuator",
+ value: ">",
+ range: [6, 7],
+ loc: { start: { line: 1, column: 6 }, end: { line: 1, column: 7 } },
+ },
+ {
+ type: "Keyword",
+ value: "this",
+ range: [8, 12],
+ loc: {
+ start: { line: 1, column: 8 },
+ end: { line: 1, column: 12 },
+ },
+ },
+ {
+ type: "Punctuator",
+ value: ".",
+ range: [12, 13],
+ loc: {
+ start: { line: 1, column: 12 },
+ end: { line: 1, column: 13 },
+ },
+ },
+ {
+ type: "Identifier",
+ value: "blah",
+ range: [13, 17],
+ loc: {
+ start: { line: 1, column: 13 },
+ end: { line: 1, column: 17 },
+ },
+ },
+ ],
+ comments: [],
+});
-module.exports = function(context) {
- return {
- Program: function(node) {
- context.report({
- node: node,
- message: "ERROR",
- fix: function(fixer) {
- return fixer.insertTextAfter(node, "this is a syntax error.");
- }
- });
- }
- };
+module.exports = {
+ meta: {
+ schema: [],
+ fixable: "code"
+ },
+ create(context) {
+ return {
+ Program: function(node) {
+ context.report({
+ node: node,
+ message: "ERROR",
+ fix: function(fixer) {
+ return fixer.insertTextAfter(node, "this is a syntax error.");
+ }
+ });
+ }
+ };
+ }
};
-module.exports.schema = [];
module.exports = function() {
-
- "use strict";
- return (null).something;
+ throw new Error("Boom!");
};
"use strict";
-module.exports = context => {
- return {
- Program(node) {
- context.report({
- node,
- message: "No programs allowed."
- });
+module.exports = {
+ meta: {
+ fixable: "code"
+ },
+ create(context) {
+ return {
+ Program(node) {
+ context.report({
+ node,
+ message: "No programs allowed."
+ });
- context.report({
- node,
- message: "Seriously, no programs allowed.",
- fix: fixer => fixer.remove(node)
- });
+ context.report({
+ node,
+ message: "Seriously, no programs allowed.",
+ fix: fixer => fixer.remove(node)
+ });
+ }
}
}
};
"use strict";
module.exports.basic = {
+ meta: { hasSuggestions: true },
create(context) {
return {
Identifier(node) {
avoidFoo: "Avoid using identifiers named '{{ name }}'.",
unused: "An unused key",
renameFoo: "Rename identifier 'foo' to '{{ newName }}'"
- }
+ },
+ hasSuggestions: true
},
create(context) {
return {
}
};
-
+module.exports.withoutHasSuggestionsProperty = {
+ create(context) {
+ return {
+ Identifier(node) {
+ context.report({
+ node,
+ message: "some message",
+ suggest: [{ desc: "some suggestion", fix: fixer => fixer.replaceText(node, 'bar') }]
+ });
+ }
+ };
+ }
+};
"use strict";
+//-----------------------------------------------------------------------------
+// Requirements
+//-----------------------------------------------------------------------------
+
const assert = require("chai").assert,
api = require("../../lib/api");
+//-----------------------------------------------------------------------------
+// Tests
+//-----------------------------------------------------------------------------
+
describe("api", () => {
it("should have RuleTester exposed", () => {
assert.isFunction(api.RuleTester);
});
- it("should have CLIEngine exposed", () => {
- assert.isFunction(api.CLIEngine);
+ 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.isObject(api.linter);
+ it("should have Linter exposed", () => {
+ assert.isFunction(api.Linter);
});
it("should have SourceCode exposed", () => {
fs = require("fs"),
os = require("os"),
hash = require("../../../lib/cli-engine/hash"),
- { CascadingConfigArrayFactory } = require("@eslint/eslintrc/lib/cascading-config-array-factory"),
+ {
+ Legacy: {
+ CascadingConfigArrayFactory
+ }
+ } = require("@eslint/eslintrc"),
{ unIndent, createCustomTeardown } = require("../../_utils");
const proxyquire = require("proxyquire").noCallThru().noPreserveCache();
originalDir = process.cwd(),
fixtureDir = path.resolve(fs.realpathSync(os.tmpdir()), "eslint/fixtures");
- /** @type {import("../../../lib/cli-engine")["CLIEngine"]} */
+ /** @type {import("../../../lib/cli-engine").CLIEngine} */
let CLIEngine;
- /** @type {import("../../../lib/cli-engine/cli-engine")["getCLIEngineInternalSlots"]} */
+ /** @type {import("../../../lib/cli-engine/cli-engine").getCLIEngineInternalSlots} */
let getCLIEngineInternalSlots;
/**
* @private
*/
function cliEngineWithPlugins(options) {
- const engine = new CLIEngine(options);
-
- // load the mocked plugins
- engine.addPlugin(examplePluginName, examplePlugin);
- engine.addPlugin(examplePluginNameWithNamespace, examplePlugin);
- engine.addPlugin(examplePreprocessorName, require("../../fixtures/processors/custom-processor"));
-
- return engine;
+ 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
* 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
+ this.timeout(60 * 1000); // eslint-disable-line no-invalid-this -- Mocha API
shell.mkdir("-p", fixtureDir);
shell.cp("-r", "./tests/fixtures/.", fixtureDir);
});
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
+ // 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`);
});
it("should not modify baseConfig when format is specified", () => {
const customBaseConfig = { root: true };
- new CLIEngine({ baseConfig: customBaseConfig, format: "foo" }); // eslint-disable-line no-new
+ new CLIEngine({ baseConfig: customBaseConfig, format: "foo" }); // eslint-disable-line no-new -- Test side effects
assert.deepStrictEqual(customBaseConfig, { root: true });
});
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);
messages: [],
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: []
],
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: []
],
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: []
],
errorCount: 1,
warningCount: 0,
+ fatalErrorCount: 1,
fixableErrorCount: 0,
fixableWarningCount: 0,
source: "var bar ="
],
errorCount: 1,
warningCount: 0,
+ fatalErrorCount: 1,
fixableErrorCount: 0,
fixableWarningCount: 0,
usedDeprecatedRules: []
],
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: []
const Module = require("module");
let originalFindPath = null;
- /* eslint-disable no-underscore-dangle */
+ /* eslint-disable no-underscore-dangle -- Private Node API overriding */
before(() => {
originalFindPath = Module._findPath;
Module._findPath = function(id, ...otherArgs) {
after(() => {
Module._findPath = originalFindPath;
});
- /* eslint-enable no-underscore-dangle */
+ /* 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") });
describe("executeOnFiles()", () => {
- /** @type {InstanceType<import("../../../lib/cli-engine")["CLIEngine"]>} */
+ /** @type {InstanceType<import("../../../lib/cli-engine").CLIEngine>} */
let engine;
it("should use correct parser when custom parser is specified", () => {
messages: [],
errorCount: 0,
warningCount: 0,
+ fatalErrorCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0,
output: "true ? \"yes\" : \"no\";\n"
messages: [],
errorCount: 0,
warningCount: 0,
+ fatalErrorCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0
},
],
errorCount: 1,
warningCount: 0,
+ fatalErrorCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0,
output: "var msg = \"hi\";\nif (msg == \"hi\") {\n\n}\n"
],
errorCount: 1,
warningCount: 0,
+ fatalErrorCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0,
output: "var msg = \"hi\" + foo;\n"
useEslintrc: false,
plugins: ["test"],
rules: { "test/example-rule": 1 }
+ }, {
+ preloadedPlugins: {
+ "eslint-plugin-test": {
+ rules: {
+ "example-rule": require("../../fixtures/rules/custom-rule")
+ }
+ }
+ }
});
- engine.addPlugin("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);
},
extensions: ["js", "txt"],
cwd: path.join(fixtureDir, "..")
- });
-
- engine.addPlugin("test-processor", {
- processors: {
- ".txt": {
- preprocess(text) {
- return [text];
- },
- postprocess(messages) {
- return messages[0];
+ }, {
+ preloadedPlugins: {
+ "test-processor": {
+ processors: {
+ ".txt": {
+ preprocess(text) {
+ return [text];
+ },
+ postprocess(messages) {
+ return messages[0];
+ }
+ }
}
}
}
},
extensions: ["js", "txt"],
cwd: path.join(fixtureDir, "..")
- });
-
- engine.addPlugin("test-processor", {
- processors: {
- ".txt": {
- preprocess(text) {
- return [text.replace("a()", "b()")];
- },
- postprocess(messages) {
- messages[0][0].ruleId = "post-processed";
- return messages[0];
+ }, {
+ preloadedPlugins: {
+ "test-processor": {
+ processors: {
+ ".txt": {
+ preprocess(text) {
+ return [text.replace("a()", "b()")];
+ },
+ postprocess(messages) {
+ messages[0][0].ruleId = "post-processed";
+ return messages[0];
+ }
+ }
}
}
}
},
extensions: ["js", "txt"],
ignore: false
- });
-
- engine.addPlugin("test-processor", {
- processors: {
- ".txt": {
- preprocess(text) {
- return [text.replace("a()", "b()")];
- },
- postprocess(messages) {
- messages[0][0].ruleId = "post-processed";
- return messages[0];
+ }, {
+ preloadedPlugins: {
+ "test-processor": {
+ processors: {
+ ".txt": {
+ preprocess(text) {
+ return [text.replace("a()", "b()")];
+ },
+ postprocess(messages) {
+ messages[0][0].ruleId = "post-processed";
+ return messages[0];
+ }
+ }
}
}
}
extensions: ["js", "txt"],
ignore: false,
fix: true
- });
-
- engine.addPlugin("test-processor", {
- processors: {
- ".html": Object.assign({ supportsAutofix: true }, HTML_PROCESSOR)
+ }, {
+ preloadedPlugins: {
+ "test-processor": {
+ processors: {
+ ".html": Object.assign({ supportsAutofix: true }, HTML_PROCESSOR)
+ }
+ }
}
});
extensions: ["js", "txt"],
ignore: false,
fix: true
+ }, {
+ preloadedPlugins: {
+ "test-processor": {
+ processors: {
+ ".html": HTML_PROCESSOR
+ }
+ }
+ }
});
- engine.addPlugin("test-processor", { processors: { ".html": HTML_PROCESSOR } });
-
const report = engine.executeOnText("<script>foo</script>", "foo.html");
assert.strictEqual(report.results[0].messages.length, 1);
},
extensions: ["js", "txt"],
ignore: false
- });
-
- engine.addPlugin("test-processor", {
- processors: {
- ".html": Object.assign({ supportsAutofix: true }, HTML_PROCESSOR)
+ }, {
+ preloadedPlugins: {
+ "test-processor": {
+ processors: {
+ ".html": Object.assign({ supportsAutofix: true }, HTML_PROCESSOR)
+ }
+ }
}
});
assert.throw(() => {
try {
- // eslint-disable-next-line no-new
+ // eslint-disable-next-line no-new -- Check for throwing
new CLIEngine({ cwd });
} catch (error) {
assert.strictEqual(error.messageTemplate, "failed-to-read-json");
const cwd = getFixturePath("ignored-paths", "bad-package-json-ignore");
assert.throws(() => {
- // eslint-disable-next-line no-new
+ // eslint-disable-next-line no-new -- Check for throwing
new CLIEngine({ cwd });
}, "Package.json eslintIgnore property requires an array of paths");
});
const ignorePath = getFixturePath("ignored-paths", "not-a-directory", ".foobaz");
assert.throws(() => {
- // eslint-disable-next-line no-new
+ // eslint-disable-next-line no-new -- Check for throwing
new CLIEngine({ ignorePath, cwd });
}, "Cannot read .eslintignore file");
});
assert.isFunction(formatter);
});
- it("should return null when a customer formatter doesn't exist", () => {
+ it("should return null when a custom formatter doesn't exist", () => {
const engine = new CLIEngine(),
formatterPath = getFixturePath("formatters", "doesntexist.js"),
fullFormatterPath = path.resolve(formatterPath);
}, `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(engine.getRules().has("node/no-deprecated-api"), "node/no-deprecated-api is present");
});
- it("should expose the rules of the plugin that is added by 'addPlugin'.", () => {
- const engine = new CLIEngine({ plugins: ["foo"] });
-
- engine.addPlugin("foo", require("eslint-plugin-node"));
+ it("should expose the list of rules from a preloaded plugin", () => {
+ const engine = new CLIEngine({
+ plugins: ["foo"]
+ }, {
+ preloadedPlugins: {
+ foo: require("eslint-plugin-node")
+ }
+ });
assert(engine.getRules().has("foo/no-deprecated-api"), "foo/no-deprecated-api is present");
});
const config = {
envs: ["browser"],
ignore: true,
+ useEslintrc: false,
allowInlineConfig: false,
rules: {
"eol-last": 0,
const config = {
envs: ["browser"],
ignore: true,
+ useEslintrc: false,
// allowInlineConfig: true is the default
rules: {
message: "Unused eslint-disable directive (no problems were reported).",
line: 1,
column: 1,
+ fix: {
+ range: [0, 20],
+ text: " "
+ },
severity: 2,
nodeType: null
}
],
errorCount: 1,
warningCount: 0,
- fixableErrorCount: 0,
+ fatalErrorCount: 0,
+ fixableErrorCount: 1,
fixableWarningCount: 0,
source: "/* eslint-disable */"
}
],
errorCount: 1,
warningCount: 0,
- fixableErrorCount: 0,
+ fatalErrorCount: 0,
+ fixableErrorCount: 1,
fixableWarningCount: 0,
usedDeprecatedRules: []
}
}
],
source: "a == b",
- warningCount: 0
+ warningCount: 0,
+ fatalErrorCount: 0
}
]);
});
fixableErrorCount: 0,
fixableWarningCount: 0,
messages: [],
- warningCount: 0
+ warningCount: 0,
+ fatalErrorCount: 0
}
]);
});
fixableErrorCount: 0,
fixableWarningCount: 0,
messages: [],
- warningCount: 0
+ warningCount: 0,
+ fatalErrorCount: 0
}
]);
});
}
],
source: "a == b",
- warningCount: 0
+ warningCount: 0,
+ fatalErrorCount: 0
}
]);
});
*/
"use strict";
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
const fs = require("fs");
const path = require("path");
const os = require("os");
const { assert } = require("chai");
const sh = require("shelljs");
-const { CascadingConfigArrayFactory } =
- require("@eslint/eslintrc/lib/cascading-config-array-factory");
+const {
+ Legacy: {
+ CascadingConfigArrayFactory
+ }
+} = require("@eslint/eslintrc");
const { createCustomTeardown } = require("../../_utils");
const { FileEnumerator } = require("../../../lib/cli-engine/file-enumerator");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
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'", () => {
describe("if 'lib/*.js' was given,", () => {
- /** @type {Array<{config:(typeof import('../../../lib/cli-engine'))["ConfigArray"], filePath:string, ignored:boolean}>} */
+ /** @type {Array<{config:(typeof import('../../../lib/cli-engine')).ConfigArray, filePath:string, ignored:boolean}>} */
let list;
beforeEach(() => {
describe("if 'lib/**/*.js' was given,", () => {
- /** @type {Array<{config:(typeof import('../../../lib/cli-engine'))["ConfigArray"], filePath:string, ignored:boolean}>} */
+ /** @type {Array<{config:(typeof import('../../../lib/cli-engine')).ConfigArray, filePath:string, ignored:boolean}>} */
let list;
beforeEach(() => {
describe("if 'lib/*.js' and 'test/*.js' were given,", () => {
- /** @type {Array<{config:(typeof import('../../../lib/cli-engine'))["ConfigArray"], filePath:string, ignored:boolean}>} */
+ /** @type {Array<{config:(typeof import('../../../lib/cli-engine')).ConfigArray, filePath:string, ignored:boolean}>} */
let list;
beforeEach(() => {
* 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
+ 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);
+++ /dev/null
-/**
- * @fileoverview Tests for codeframe reporter.
- * @author Vitor Balocco
- */
-
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const assert = require("chai").assert;
-const sinon = require("sinon");
-const proxyquire = require("proxyquire");
-const chalk = require("chalk");
-const path = require("path");
-const stripAnsi = require("strip-ansi");
-
-// Chalk protects its methods so we need to inherit from it for Sinon to work.
-const chalkStub = Object.create(chalk, {
- 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/codeframe", { chalk: chalkStub });
-
-//------------------------------------------------------------------------------
-// Tests
-//------------------------------------------------------------------------------
-
-describe("formatter:codeframe", () => {
- afterEach(() => {
- sinon.verifyAndRestore();
- });
-
- describe("when passed no messages", () => {
- const code = [{
- filePath: "foo.js",
- messages: [],
- errorCount: 0,
- warningCount: 0
- }];
-
- it("should return nothing", () => {
- const result = formatter(code);
-
- assert.strictEqual(result, "");
- });
- });
-
- describe("when passed a single warning message", () => {
- const code = [{
- filePath: path.join(process.cwd(), "lib", "foo.js"),
- source: "var foo = 1;\n var bar = 2;\n",
- messages: [{
- message: "Unexpected foo.",
- severity: 1,
- line: 1,
- column: 5,
- ruleId: "foo"
- }],
- errorCount: 0,
- warningCount: 1,
- fixableErrorCount: 0,
- fixableWarningCount: 0
- }];
-
- it("should return a string in the correct format for warnings", () => {
- const result = formatter(code);
-
- assert.strictEqual(stripAnsi(result), [
- `warning: Unexpected foo (foo) at ${path.join("lib", "foo.js")}:1:5:`,
- "> 1 | var foo = 1;",
- " | ^",
- " 2 | var bar = 2;",
- " 3 | ",
- "\n",
- "1 warning found."
- ].join("\n"));
- });
-
- it("should return bold yellow summary when there are only warnings", () => {
- sinon.spy(chalkStub.yellow, "bold");
- sinon.spy(chalkStub.red, "bold");
-
- formatter(code);
-
- assert.strictEqual(chalkStub.yellow.bold.callCount, 1);
- assert.strictEqual(chalkStub.red.bold.callCount, 0);
- });
-
- describe("when the warning is fixable", () => {
- beforeEach(() => {
- code[0].fixableWarningCount = 1;
- });
-
- it("should return a string in the correct format", () => {
- const result = formatter(code);
-
- assert.strictEqual(stripAnsi(result), [
- `warning: Unexpected foo (foo) at ${path.join("lib", "foo.js")}:1:5:`,
- "> 1 | var foo = 1;",
- " | ^",
- " 2 | var bar = 2;",
- " 3 | ",
- "\n",
- "1 warning found.",
- "1 warning potentially fixable with the `--fix` option."
- ].join("\n"));
- });
- });
- });
-
- describe("when passed a single error message", () => {
- const code = [{
- filePath: path.join(process.cwd(), "lib", "foo.js"),
- source: "var foo = 1;\n var bar = 2;\n",
- messages: [{
- message: "Unexpected foo.",
- severity: 2,
- line: 1,
- column: 5,
- ruleId: "foo"
- }],
- errorCount: 1,
- warningCount: 0
- }];
-
- it("should return a string in the correct format for errors", () => {
- const result = formatter(code);
-
- assert.strictEqual(stripAnsi(result), [
- `error: Unexpected foo (foo) at ${path.join("lib", "foo.js")}:1:5:`,
- "> 1 | var foo = 1;",
- " | ^",
- " 2 | var bar = 2;",
- " 3 | ",
- "\n",
- "1 error found."
- ].join("\n"));
- });
-
- it("should return bold red summary when there are errors", () => {
- sinon.spy(chalkStub.yellow, "bold");
- sinon.spy(chalkStub.red, "bold");
-
- formatter(code);
-
- assert.strictEqual(chalkStub.yellow.bold.callCount, 0);
- assert.strictEqual(chalkStub.red.bold.callCount, 1);
- });
- });
-
- describe("when passed a message that ends with ' .'", () => {
- const code = [{
- filePath: "foo.js",
- messages: [{
- ruleId: "foo",
- message: "Unexpected .",
- severity: 2,
- source: "foo"
- }],
- errorCount: 1,
- warningCount: 0
- }];
-
- it("should return a string in the correct format (retaining the ' .')", () => {
- const result = formatter(code);
-
- assert.strictEqual(stripAnsi(result), "error: Unexpected . (foo) at foo.js\n\n\n1 error found.");
- });
- });
-
- describe("when passed multiple messages", () => {
- const code = [{
- filePath: "foo.js",
- source: "const foo = 1\n",
- messages: [{
- message: "Missing semicolon.",
- severity: 2,
- line: 1,
- column: 14,
- ruleId: "semi"
- }, {
- message: "'foo' is assigned a value but never used.",
- severity: 2,
- line: 1,
- column: 7,
- ruleId: "no-unused-vars"
- }],
- errorCount: 2,
- warningCount: 0
- }];
-
- it("should return a string with multiple entries", () => {
- const result = formatter(code);
-
- assert.strictEqual(stripAnsi(result), [
- "error: Missing semicolon (semi) at foo.js:1:14:",
- "> 1 | const foo = 1",
- " | ^",
- " 2 | ",
- "\n",
- "error: 'foo' is assigned a value but never used (no-unused-vars) at foo.js:1:7:",
- "> 1 | const foo = 1",
- " | ^",
- " 2 | ",
- "\n",
- "2 errors found."
- ].join("\n"));
- });
-
- it("should return bold red summary when at least 1 of the messages is an error", () => {
- sinon.spy(chalkStub.yellow, "bold");
- sinon.spy(chalkStub.red, "bold");
- code[0].messages[0].severity = 1;
- code[0].warningCount = 1;
- code[0].errorCount = 1;
-
- formatter(code);
-
- assert.strictEqual(chalkStub.yellow.bold.callCount, 0);
- assert.strictEqual(chalkStub.red.bold.callCount, 1);
- });
- });
-
- describe("when passed one file with 1 message and fixes applied", () => {
- const code = [{
- filePath: "foo.js",
- messages: [{
- ruleId: "no-unused-vars",
- severity: 2,
- message: "'foo' is assigned a value but never used.",
- line: 4,
- column: 11,
- source: " const foo = 1;"
- }],
- errorCount: 1,
- warningCount: 0,
- output: "function foo() {\n\n // a comment\n const foo = 1;\n}\n\n"
- }];
-
- it("should return a string with code preview pointing to the correct location after fixes", () => {
- const result = formatter(code);
-
- assert.strictEqual(stripAnsi(result), [
- "error: 'foo' is assigned a value but never used (no-unused-vars) at foo.js:4:11:",
- " 2 | ",
- " 3 | // a comment",
- "> 4 | const foo = 1;",
- " | ^",
- " 5 | }",
- " 6 | ",
- " 7 | ",
- "\n",
- "1 error found."
- ].join("\n"));
- });
- });
-
- describe("when passed multiple files with 1 message each", () => {
- const code = [{
- filePath: "foo.js",
- source: "const foo = 1\n",
- messages: [{
- message: "Missing semicolon.",
- severity: 2,
- line: 1,
- column: 14,
- ruleId: "semi"
- }],
- errorCount: 1,
- warningCount: 0
- }, {
- filePath: "bar.js",
- source: "const bar = 2\n",
- messages: [{
- message: "Missing semicolon.",
- severity: 2,
- line: 1,
- column: 14,
- ruleId: "semi"
- }],
- errorCount: 1,
- warningCount: 0
- }];
-
- it("should return a string with multiple entries", () => {
- const result = formatter(code);
-
- assert.strictEqual(stripAnsi(result), [
- "error: Missing semicolon (semi) at foo.js:1:14:",
- "> 1 | const foo = 1",
- " | ^",
- " 2 | ",
- "\n",
- "error: Missing semicolon (semi) at bar.js:1:14:",
- "> 1 | const bar = 2",
- " | ^",
- " 2 | ",
- "\n",
- "2 errors found."
- ].join("\n"));
- });
- });
-
- describe("when passed a fatal error message", () => {
- const code = [{
- filePath: "foo.js",
- source: "e{}\n",
- messages: [{
- ruleId: null,
- fatal: true,
- severity: 2,
- source: "e{}",
- message: "Parsing error: Unexpected token {",
- line: 1,
- column: 2
- }],
- errorCount: 1,
- warningCount: 0
- }];
-
- it("should return a string in the correct format", () => {
- const result = formatter(code);
-
- assert.strictEqual(stripAnsi(result), [
- "error: Parsing error: Unexpected token { at foo.js:1:2:",
- "> 1 | e{}",
- " | ^",
- " 2 | ",
- "\n",
- "1 error found."
- ].join("\n"));
- });
- });
-
- describe("when passed one file not found message", () => {
- const code = [{
- filePath: "foo.js",
- messages: [{
- fatal: true,
- message: "Couldn't find foo.js."
- }],
- errorCount: 1,
- warningCount: 0
- }];
-
- it("should return a string without code preview (codeframe)", () => {
- const result = formatter(code);
-
- assert.strictEqual(stripAnsi(result), "error: Couldn't find foo.js at foo.js\n\n\n1 error found.");
- });
- });
-
- describe("when passed a single message with no line or column", () => {
- const code = [{
- filePath: "foo.js",
- messages: [{
- ruleId: "foo",
- message: "Unexpected foo.",
- severity: 2,
- source: "foo"
- }],
- errorCount: 1,
- warningCount: 0
- }];
-
- it("should return a string without code preview (codeframe)", () => {
- const result = formatter(code);
-
- assert.strictEqual(stripAnsi(result), "error: Unexpected foo (foo) at foo.js\n\n\n1 error found.");
- });
-
- it("should output filepath but without 'line:column' appended", () => {
- const result = formatter(code);
-
- assert.strictEqual(stripAnsi(result), "error: Unexpected foo (foo) at foo.js\n\n\n1 error found.");
- });
- });
-
-
- 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 potentially fixable with the `--fix` option.");
- });
-
- 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, "2 warnings potentially fixable with the `--fix` option.");
- });
-
- 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.");
- });
- });
-
-});
* @author Jamund Ferguson
*/
-/* jshint node:true */
-
"use strict";
//------------------------------------------------------------------------------
proxyquire = require("proxyquire"),
sinon = require("sinon");
+//-----------------------------------------------------------------------------
+// Helpers
+//-----------------------------------------------------------------------------
+
/*
* Chalk protects its methods so we need to inherit from it
* for Sinon to work.
+++ /dev/null
-/**
- * @fileoverview Tests for "table" reporter.
- * @author Gajus Kuizinas <gajus@gajus.com>
- */
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const assert = require("chai").assert;
-const chalk = require("chalk");
-const formatter = require("../../../../lib/cli-engine/formatters/table");
-
-//------------------------------------------------------------------------------
-// Tests
-//------------------------------------------------------------------------------
-
-describe("formatter:table", () => {
- const originalColorLevel = chalk.level;
-
- before(() => {
- chalk.level = 0;
- });
-
- after(() => {
- chalk.level = originalColorLevel;
- });
-
- describe("when passed no messages", () => {
- const code = [
- {
- filePath: "foo.js",
- messages: [],
- errorCount: 0,
- warningCount: 0
- }
- ];
-
- it("should return a table of error and warning count with no messages", () => {
- const expectedOutput = [
- "",
- "╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗",
- "║ 0 Errors ║",
- "╟────────────────────────────────────────────────────────────────────────────────────────────────────────────────╢",
- "║ 0 Warnings ║",
- "╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝",
- ""
- ].join("\n");
-
- const result = formatter(code);
-
- assert.strictEqual(result, expectedOutput);
- });
- });
-
- describe("when passed a single message", () => {
- it("should return a string in the correct format for errors", () => {
- const code = [
- {
- filePath: "foo.js",
- messages: [
- {
- message: "Unexpected foo.",
- severity: 2,
- line: 5,
- column: 10,
- ruleId: "foo"
- }
- ],
- errorCount: 1,
- warningCount: 0
- }
- ];
-
- const expectedOutput = [
- "",
- "foo.js",
- "",
- "║ Line │ Column │ Type │ Message │ Rule ID ║",
- "╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢",
- "║ 5 │ 10 │ error │ Unexpected foo. │ foo ║",
- "",
- "╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗",
- "║ 1 Error ║",
- "╟────────────────────────────────────────────────────────────────────────────────────────────────────────────────╢",
- "║ 0 Warnings ║",
- "╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝",
- ""
- ].join("\n");
-
- const result = formatter(code);
-
- assert.strictEqual(result, expectedOutput);
- });
-
- it("should return a string in the correct format for warnings", () => {
- const code = [
- {
- filePath: "foo.js",
- messages: [
- {
- message: "Unexpected foo.",
- severity: 1,
- line: 5,
- column: 10,
- ruleId: "foo"
- }
- ],
- errorCount: 0,
- warningCount: 1
- }
- ];
-
- const expectedOutput = [
- "",
- "foo.js",
- "",
- "║ Line │ Column │ Type │ Message │ Rule ID ║",
- "╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢",
- "║ 5 │ 10 │ warning │ Unexpected foo. │ foo ║",
- "",
- "╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗",
- "║ 0 Errors ║",
- "╟────────────────────────────────────────────────────────────────────────────────────────────────────────────────╢",
- "║ 1 Warning ║",
- "╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝",
- ""
- ].join("\n");
-
- const result = formatter(code);
-
- assert.strictEqual(result, expectedOutput);
- });
- });
-
- describe("when passed a fatal error message", () => {
- it("should return a string in the correct format", () => {
- const code = [
- {
- filePath: "foo.js",
- messages: [
- {
- fatal: true,
- message: "Unexpected foo.",
- line: 5,
- column: 10,
- ruleId: "foo"
- }
- ],
- errorCount: 1,
- warningCount: 0
- }
- ];
-
- const expectedOutput = [
- "",
- "foo.js",
- "",
- "║ Line │ Column │ Type │ Message │ Rule ID ║",
- "╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢",
- "║ 5 │ 10 │ error │ Unexpected foo. │ foo ║",
- "",
- "╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗",
- "║ 1 Error ║",
- "╟────────────────────────────────────────────────────────────────────────────────────────────────────────────────╢",
- "║ 0 Warnings ║",
- "╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝",
- ""
- ].join("\n");
-
- const result = formatter(code);
-
- assert.strictEqual(result, expectedOutput);
- });
- });
-
- describe("when passed multiple messages", () => {
- it("should return a string with multiple entries", () => {
- const code = [
- {
- filePath: "foo.js",
- messages: [
- {
- message: "Unexpected foo.",
- severity: 2,
- line: 5,
- column: 10,
- ruleId: "foo"
- },
- {
- message: "Unexpected bar.",
- severity: 1,
- line: 6,
- column: 11,
- ruleId: "bar"
- }
- ],
- errorCount: 1,
- warningCount: 1
- }
- ];
-
- const expectedOutput = [
- "",
- "foo.js",
- "",
- "║ Line │ Column │ Type │ Message │ Rule ID ║",
- "╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢",
- "║ 5 │ 10 │ error │ Unexpected foo. │ foo ║",
- "║ 6 │ 11 │ warning │ Unexpected bar. │ bar ║",
- "",
- "╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗",
- "║ 1 Error ║",
- "╟────────────────────────────────────────────────────────────────────────────────────────────────────────────────╢",
- "║ 1 Warning ║",
- "╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝",
- ""
- ].join("\n");
-
- const result = formatter(code);
-
- assert.strictEqual(result, expectedOutput);
- });
- });
-
- describe("when passed multiple files with 1 message each", () => {
- it("should return a string with multiple entries", () => {
- const code = [
- {
- filePath: "foo.js",
- messages: [
- {
- message: "Unexpected foo.",
- severity: 2,
- line: 5,
- column: 10,
- ruleId: "foo"
- }
- ],
- errorCount: 1,
- warningCount: 0
- }, {
- filePath: "bar.js",
- messages: [
- {
- message: "Unexpected bar.",
- severity: 1,
- line: 6,
- column: 11,
- ruleId: "bar"
- }
- ],
- errorCount: 0,
- warningCount: 1
- }
- ];
-
- const expectedOutput = [
- "",
- "foo.js",
- "",
- "║ Line │ Column │ Type │ Message │ Rule ID ║",
- "╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢",
- "║ 5 │ 10 │ error │ Unexpected foo. │ foo ║",
- "",
- "bar.js",
- "",
- "║ Line │ Column │ Type │ Message │ Rule ID ║",
- "╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢",
- "║ 6 │ 11 │ warning │ Unexpected bar. │ bar ║",
- "",
- "╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗",
- "║ 1 Error ║",
- "╟────────────────────────────────────────────────────────────────────────────────────────────────────────────────╢",
- "║ 1 Warning ║",
- "╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝",
- ""
- ].join("\n");
-
- const result = formatter(code);
-
- assert.strictEqual(result, expectedOutput);
- });
- });
-
- describe("when passed one file not found message", () => {
- it("should return a string without line and column (0, 0)", () => {
- const code = [
- {
- filePath: "foo.js",
- messages: [
- {
- fatal: true,
- message: "Couldn't find foo.js."
- }
- ],
- errorCount: 1,
- warningCount: 0
- }
- ];
-
- const expectedOutput = [
- "",
- "foo.js",
- "",
- "║ Line │ Column │ Type │ Message │ Rule ID ║",
- "╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢",
- "║ 0 │ 0 │ error │ Couldn't find foo.js. │ ║",
- "",
- "╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗",
- "║ 1 Error ║",
- "╟────────────────────────────────────────────────────────────────────────────────────────────────────────────────╢",
- "║ 0 Warnings ║",
- "╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝",
- ""
- ].join("\n");
-
- const result = formatter(code);
-
- assert.strictEqual(result, expectedOutput);
- });
- });
-});
"use strict";
+//-----------------------------------------------------------------------------
+// Requirements
+//-----------------------------------------------------------------------------
+
const assert = require("chai").assert;
const loadRules = require("../../../lib/cli-engine/load-rules");
+//-----------------------------------------------------------------------------
+// Tests
+//-----------------------------------------------------------------------------
+
describe("when given an invalid rules directory", () => {
it("should throw an error", () => {
assert.throws(() => {
* 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
+ 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);
const exit = await cli.execute(code);
assert.strictEqual(exit, 2);
- }, /Error while loading rule 'custom-rule': Cannot read property/u);
+ }, /Error while loading rule 'custom-rule': Boom!/u);
});
it("should return a warning when rule is matched", async () => {
});
});
+ describe("when given the exit-on-fatal-error flag", () => {
+ it("should not change exit code if no fatal errors are reported", async () => {
+ const filePath = getFixturePath("exit-on-fatal-error", "no-fatal-error.js");
+ const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`);
+
+ assert.strictEqual(exitCode, 0);
+ });
+
+ it("should exit with exit code 1 if no fatal errors are found, but rule violations are found", 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}`);
+
+ assert.strictEqual(exitCode, 1);
+ });
+
+ it("should exit with exit code 2 if fatal error is found", async () => {
+ const filePath = getFixturePath("exit-on-fatal-error", "fatal-error.js");
+ const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`);
+
+ assert.strictEqual(exitCode, 2);
+ });
+
+ it("should exit with exit code 2 if fatal error is found in any file", async () => {
+ const filePath = getFixturePath("exit-on-fatal-error");
+ const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`);
+
+ assert.strictEqual(exitCode, 2);
+ });
+
+
+ });
+
describe("when passed --no-inline-config", () => {
let localCLI;
--- /dev/null
+/**
+ * @fileoverview Tests for FlatConfigArray
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//-----------------------------------------------------------------------------
+// Requirements
+//-----------------------------------------------------------------------------
+
+const { FlatConfigArray } = require("../../../lib/config/flat-config-array");
+const assert = require("chai").assert;
+const allConfig = require("../../../conf/eslint-all");
+const recommendedConfig = require("../../../conf/eslint-recommended");
+
+//-----------------------------------------------------------------------------
+// Helpers
+//-----------------------------------------------------------------------------
+
+const baseConfig = {
+ plugins: {
+ "@": {
+ rules: {
+ foo: {
+ schema: {
+ type: "array",
+ items: [
+ {
+ enum: ["always", "never"]
+ }
+ ],
+ minItems: 0,
+ maxItems: 1
+ }
+
+ },
+ bar: {
+
+ },
+ baz: {
+
+ },
+
+ // old-style
+ boom() {},
+
+ foo2: {
+ schema: {
+ type: "array",
+ items: {
+ type: "string"
+ },
+ uniqueItems: true,
+ minItems: 1
+ }
+ }
+ }
+ },
+ test1: {
+ rules: {
+ match: {}
+ }
+ },
+ test2: {
+ rules: {
+ nomatch: {}
+ }
+ }
+ }
+};
+
+/**
+ * Creates a config array with the correct default options.
+ * @param {*[]} configs An array of configs to use in the config array.
+ * @returns {FlatConfigArray} The config array;
+ */
+function createFlatConfigArray(configs) {
+ return new FlatConfigArray(configs, {
+ basePath: __dirname,
+ baseConfig
+ });
+}
+
+/**
+ * Asserts that a given set of configs will be merged into the given
+ * result config.
+ * @param {*[]} values An array of configs to use in the config array.
+ * @param {Object} result The expected merged result of the configs.
+ * @returns {void}
+ * @throws {AssertionError} If the actual result doesn't match the
+ * expected result.
+ */
+async function assertMergedResult(values, result) {
+ const configs = createFlatConfigArray(values);
+
+ await configs.normalize();
+
+ const config = configs.getConfig("foo.js");
+
+ assert.deepStrictEqual(config, result);
+}
+
+/**
+ * Asserts that a given set of configs results in an invalid config.
+ * @param {*[]} values An array of configs to use in the config array.
+ * @param {string|RegExp} message The expected error message.
+ * @returns {void}
+ * @throws {AssertionError} If the config is valid or if the error
+ * has an unexpected message.
+ */
+async function assertInvalidConfig(values, message) {
+ const configs = createFlatConfigArray(values);
+
+ await configs.normalize();
+
+ assert.throws(() => {
+ configs.getConfig("foo.js");
+ }, message);
+}
+
+/**
+ * Normalizes the rule configs to an array with severity to match
+ * how Flat Config merges rule options.
+ * @param {Object} rulesConfig The rules config portion of a config.
+ * @returns {Array} The rules config object.
+ */
+function normalizeRuleConfig(rulesConfig) {
+ const rulesConfigCopy = {
+ ...rulesConfig
+ };
+
+ for (const ruleId of Object.keys(rulesConfigCopy)) {
+ rulesConfigCopy[ruleId] = [2];
+ }
+
+ return rulesConfigCopy;
+}
+
+//-----------------------------------------------------------------------------
+// Tests
+//-----------------------------------------------------------------------------
+
+describe("FlatConfigArray", () => {
+
+ describe("Special configs", () => {
+ it("eslint:recommended is replaced with an actual config", async () => {
+ const configs = new FlatConfigArray(["eslint:recommended"], { basePath: __dirname });
+
+ await configs.normalize();
+ const config = configs.getConfig("foo.js");
+
+ assert.deepStrictEqual(config.rules, normalizeRuleConfig(recommendedConfig.rules));
+ });
+
+ it("eslint:all is replaced with an actual config", async () => {
+ const configs = new FlatConfigArray(["eslint:all"], { basePath: __dirname });
+
+ await configs.normalize();
+ const config = configs.getConfig("foo.js");
+
+ assert.deepStrictEqual(config.rules, normalizeRuleConfig(allConfig.rules));
+ });
+ });
+
+ describe("Config Properties", () => {
+
+ describe("settings", () => {
+
+ it("should merge two objects", () => assertMergedResult([
+ {
+ settings: {
+ a: true,
+ b: false
+ }
+ },
+ {
+ settings: {
+ c: true,
+ d: false
+ }
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ settings: {
+ a: true,
+ b: false,
+ c: true,
+ d: false
+ }
+ }));
+
+ it("should merge two objects when second object has overrides", () => assertMergedResult([
+ {
+ settings: {
+ a: true,
+ b: false,
+ d: [1, 2],
+ e: [5, 6]
+ }
+ },
+ {
+ settings: {
+ c: true,
+ a: false,
+ d: [3, 4]
+ }
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ settings: {
+ a: false,
+ b: false,
+ c: true,
+ d: [3, 4],
+ e: [5, 6]
+ }
+ }));
+
+ it("should deeply merge two objects when second object has overrides", () => assertMergedResult([
+ {
+ settings: {
+ object: {
+ a: true,
+ b: false
+ }
+ }
+ },
+ {
+ settings: {
+ object: {
+ c: true,
+ a: false
+ }
+ }
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ settings: {
+ object: {
+ a: false,
+ b: false,
+ c: true
+ }
+ }
+ }));
+
+ it("should merge an object and undefined into one object", () => assertMergedResult([
+ {
+ settings: {
+ a: true,
+ b: false
+ }
+ },
+ {
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ settings: {
+ a: true,
+ b: false
+ }
+ }));
+
+ it("should merge undefined and an object into one object", () => assertMergedResult([
+ {
+ },
+ {
+ settings: {
+ a: true,
+ b: false
+ }
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ settings: {
+ a: true,
+ b: false
+ }
+ }));
+
+ });
+
+ describe("plugins", () => {
+
+ const pluginA = {};
+ const pluginB = {};
+ const pluginC = {};
+
+ it("should merge two objects", () => assertMergedResult([
+ {
+ plugins: {
+ a: pluginA,
+ b: pluginB
+ }
+ },
+ {
+ plugins: {
+ c: pluginC
+ }
+ }
+ ], {
+ plugins: {
+ a: pluginA,
+ b: pluginB,
+ c: pluginC,
+ ...baseConfig.plugins
+ }
+ }));
+
+ it("should merge an object and undefined into one object", () => assertMergedResult([
+ {
+ plugins: {
+ a: pluginA,
+ b: pluginB
+ }
+ },
+ {
+ }
+ ], {
+ plugins: {
+ a: pluginA,
+ b: pluginB,
+ ...baseConfig.plugins
+ }
+ }));
+
+ it("should error when attempting to redefine a plugin", async () => {
+
+ await assertInvalidConfig([
+ {
+ plugins: {
+ a: pluginA,
+ b: pluginB
+ }
+ },
+ {
+ plugins: {
+ a: pluginC
+ }
+ }
+ ], "Cannot redefine plugin \"a\".");
+ });
+
+ it("should error when plugin is not an object", async () => {
+
+ await assertInvalidConfig([
+ {
+ plugins: {
+ a: true
+ }
+ }
+ ], "Key \"a\": Expected an object.");
+ });
+
+
+ });
+
+ describe("processor", () => {
+
+ it("should merge two values when second is a string", () => {
+
+ const stubProcessor = {
+ preprocess() {},
+ postprocess() {}
+ };
+
+ return assertMergedResult([
+ {
+ processor: {
+ preprocess() {},
+ postprocess() {}
+ }
+ },
+ {
+ plugins: {
+ markdown: {
+ processors: {
+ markdown: stubProcessor
+ }
+ }
+ },
+ processor: "markdown/markdown"
+ }
+ ], {
+ plugins: {
+ markdown: {
+ processors: {
+ markdown: stubProcessor
+ }
+ },
+ ...baseConfig.plugins
+ },
+ processor: stubProcessor
+ });
+ });
+
+ it("should merge two values when second is an object", () => {
+
+ const processor = {
+ preprocess() { },
+ postprocess() { }
+ };
+
+ return assertMergedResult([
+ {
+ processor: "markdown/markdown"
+ },
+ {
+ processor
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ processor
+ });
+ });
+
+ it("should error when an invalid string is used", async () => {
+
+ await assertInvalidConfig([
+ {
+ processor: "foo"
+ }
+ ], "pluginName/objectName");
+ });
+
+ it("should error when an empty string is used", async () => {
+
+ await assertInvalidConfig([
+ {
+ processor: ""
+ }
+ ], "pluginName/objectName");
+ });
+
+ it("should error when an invalid processor is used", async () => {
+ await assertInvalidConfig([
+ {
+ processor: {}
+ }
+ ], "Object must have a preprocess() and a postprocess() method.");
+
+ });
+
+ it("should error when a processor cannot be found in a plugin", async () => {
+ await assertInvalidConfig([
+ {
+ plugins: {
+ foo: {}
+ },
+ processor: "foo/bar"
+ }
+ ], /Could not find "bar" in plugin "foo"/u);
+
+ });
+
+ });
+
+ describe("linterOptions", () => {
+
+ it("should error when an unexpected key is found", async () => {
+
+ await assertInvalidConfig([
+ {
+ linterOptions: {
+ foo: true
+ }
+ }
+ ], "Unexpected key \"foo\" found.");
+
+ });
+
+ describe("noInlineConfig", () => {
+
+ it("should error when an unexpected value is found", async () => {
+
+ await assertInvalidConfig([
+ {
+ linterOptions: {
+ noInlineConfig: "true"
+ }
+ }
+ ], "Expected a Boolean.");
+ });
+
+ it("should merge two objects when second object has overrides", () => assertMergedResult([
+ {
+ linterOptions: {
+ noInlineConfig: true
+ }
+ },
+ {
+ linterOptions: {
+ noInlineConfig: false
+ }
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ linterOptions: {
+ noInlineConfig: false
+ }
+ }));
+
+ it("should merge an object and undefined into one object", () => assertMergedResult([
+ {
+ linterOptions: {
+ noInlineConfig: false
+ }
+ },
+ {
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ linterOptions: {
+ noInlineConfig: false
+ }
+ }));
+
+ it("should merge undefined and an object into one object", () => assertMergedResult([
+ {
+ },
+ {
+ linterOptions: {
+ noInlineConfig: false
+ }
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ linterOptions: {
+ noInlineConfig: false
+ }
+ }));
+
+
+ });
+ describe("reportUnusedDisableDirectives", () => {
+
+ it("should error when an unexpected value is found", async () => {
+
+ await assertInvalidConfig([
+ {
+ linterOptions: {
+ reportUnusedDisableDirectives: "true"
+ }
+ }
+ ], /Expected a Boolean/u);
+ });
+
+ it("should merge two objects when second object has overrides", () => assertMergedResult([
+ {
+ linterOptions: {
+ reportUnusedDisableDirectives: false
+ }
+ },
+ {
+ linterOptions: {
+ reportUnusedDisableDirectives: true
+ }
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ linterOptions: {
+ reportUnusedDisableDirectives: true
+ }
+ }));
+
+ it("should merge an object and undefined into one object", () => assertMergedResult([
+ {},
+ {
+ linterOptions: {
+ reportUnusedDisableDirectives: true
+ }
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ linterOptions: {
+ reportUnusedDisableDirectives: true
+ }
+ }));
+
+
+ });
+
+ });
+
+ describe("languageOptions", () => {
+
+ it("should error when an unexpected key is found", async () => {
+
+ await assertInvalidConfig([
+ {
+ languageOptions: {
+ foo: true
+ }
+ }
+ ], "Unexpected key \"foo\" found.");
+
+ });
+
+ it("should merge two languageOptions objects with different properties", () => assertMergedResult([
+ {
+ languageOptions: {
+ ecmaVersion: 2019
+ }
+ },
+ {
+ languageOptions: {
+ sourceType: "commonjs"
+ }
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ languageOptions: {
+ ecmaVersion: 2019,
+ sourceType: "commonjs"
+ }
+ }));
+
+ describe("ecmaVersion", () => {
+
+ it("should error when an unexpected value is found", async () => {
+
+ await assertInvalidConfig([
+ {
+ languageOptions: {
+ ecmaVersion: "true"
+ }
+ }
+ ], "Expected a number.");
+ });
+
+ it("should merge two objects when second object has overrides", () => assertMergedResult([
+ {
+ languageOptions: {
+ ecmaVersion: 2019
+ }
+ },
+ {
+ languageOptions: {
+ ecmaVersion: 2021
+ }
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ languageOptions: {
+ ecmaVersion: 2021
+ }
+ }));
+
+ it("should merge an object and undefined into one object", () => assertMergedResult([
+ {
+ languageOptions: {
+ ecmaVersion: 2021
+ }
+ },
+ {
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ languageOptions: {
+ ecmaVersion: 2021
+ }
+ }));
+
+
+ it("should merge undefined and an object into one object", () => assertMergedResult([
+ {
+ },
+ {
+ languageOptions: {
+ ecmaVersion: 2021
+ }
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ languageOptions: {
+ ecmaVersion: 2021
+ }
+ }));
+
+
+ });
+
+ describe("sourceType", () => {
+
+ it("should error when an unexpected value is found", async () => {
+
+ await assertInvalidConfig([
+ {
+ languageOptions: {
+ sourceType: "true"
+ }
+ }
+ ], "Expected \"script\", \"module\", or \"commonjs\".");
+ });
+
+ it("should merge two objects when second object has overrides", () => assertMergedResult([
+ {
+ languageOptions: {
+ sourceType: "module"
+ }
+ },
+ {
+ languageOptions: {
+ sourceType: "script"
+ }
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ languageOptions: {
+ sourceType: "script"
+ }
+ }));
+
+ it("should merge an object and undefined into one object", () => assertMergedResult([
+ {
+ languageOptions: {
+ sourceType: "script"
+ }
+ },
+ {
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ languageOptions: {
+ sourceType: "script"
+ }
+ }));
+
+
+ it("should merge undefined and an object into one object", () => assertMergedResult([
+ {
+ },
+ {
+ languageOptions: {
+ sourceType: "module"
+ }
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ languageOptions: {
+ sourceType: "module"
+ }
+ }));
+
+
+ });
+
+ describe("globals", () => {
+
+ it("should error when an unexpected value is found", async () => {
+
+ await assertInvalidConfig([
+ {
+ languageOptions: {
+ globals: "true"
+ }
+ }
+ ], "Expected an object.");
+ });
+
+ it("should error when an unexpected key value is found", async () => {
+
+ await assertInvalidConfig([
+ {
+ languageOptions: {
+ globals: {
+ foo: "truex"
+ }
+ }
+ }
+ ], "Key \"foo\": Expected \"readonly\", \"writable\", or \"off\".");
+ });
+
+ it("should error when a global has leading whitespace", async () => {
+
+ await assertInvalidConfig([
+ {
+ languageOptions: {
+ globals: {
+ " foo": "readonly"
+ }
+ }
+ }
+ ], /Global " foo" has leading or trailing whitespace/u);
+ });
+
+ it("should error when a global has trailing whitespace", async () => {
+
+ await assertInvalidConfig([
+ {
+ languageOptions: {
+ globals: {
+ "foo ": "readonly"
+ }
+ }
+ }
+ ], /Global "foo " has leading or trailing whitespace/u);
+ });
+
+ it("should merge two objects when second object has different keys", () => assertMergedResult([
+ {
+ languageOptions: {
+ globals: {
+ foo: "readonly"
+ }
+ }
+ },
+ {
+ languageOptions: {
+ globals: {
+ bar: "writable"
+ }
+ }
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ languageOptions: {
+ globals: {
+ foo: "readonly",
+ bar: "writable"
+ }
+ }
+ }));
+
+ it("should merge two objects when second object has overrides", () => assertMergedResult([
+ {
+ languageOptions: {
+ globals: {
+ foo: null
+ }
+ }
+ },
+ {
+ languageOptions: {
+ globals: {
+ foo: "writeable"
+ }
+ }
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ languageOptions: {
+ globals: {
+ foo: "writeable"
+ }
+ }
+ }));
+
+ it("should merge an object and undefined into one object", () => assertMergedResult([
+ {
+ languageOptions: {
+ globals: {
+ foo: "readable"
+ }
+ }
+ },
+ {
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ languageOptions: {
+ globals: {
+ foo: "readable"
+ }
+ }
+ }));
+
+
+ it("should merge undefined and an object into one object", () => assertMergedResult([
+ {
+ },
+ {
+ languageOptions: {
+ globals: {
+ foo: "false"
+ }
+ }
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ languageOptions: {
+ globals: {
+ foo: "false"
+ }
+ }
+ }));
+
+
+ });
+
+ describe("parser", () => {
+
+ it("should error when an unexpected value is found", async () => {
+
+ await assertInvalidConfig([
+ {
+ languageOptions: {
+ parser: true
+ }
+ }
+ ], "Expected an object or string.");
+ });
+
+ it("should error when an unexpected value is found", async () => {
+
+ await assertInvalidConfig([
+ {
+ languageOptions: {
+ parser: "true"
+ }
+ }
+ ], /Expected string in the form "pluginName\/objectName"/u);
+ });
+
+ it("should error when a plugin parser can't be found", async () => {
+
+ await assertInvalidConfig([
+ {
+ languageOptions: {
+ parser: "foo/bar"
+ }
+ }
+ ], "Key \"parser\": Could not find \"bar\" in plugin \"foo\".");
+ });
+
+ it("should error when a value doesn't have a parse() method", async () => {
+
+ await assertInvalidConfig([
+ {
+ languageOptions: {
+ parser: {}
+ }
+ }
+ ], "Expected object to have a parse() or parseForESLint() method.");
+ });
+
+ it("should merge two objects when second object has overrides", () => {
+
+ const parser = { parse() {} };
+ const stubParser = { parse() { } };
+
+ return assertMergedResult([
+ {
+ languageOptions: {
+ parser
+ }
+ },
+ {
+ plugins: {
+ "@foo/baz": {
+ parsers: {
+ bar: stubParser
+ }
+ }
+ },
+ languageOptions: {
+ parser: "@foo/baz/bar"
+ }
+ }
+ ], {
+ plugins: {
+ "@foo/baz": {
+ parsers: {
+ bar: stubParser
+ }
+ },
+ ...baseConfig.plugins
+ },
+ languageOptions: {
+ parser: stubParser
+ }
+ });
+ });
+
+ it("should merge an object and undefined into one object", () => {
+
+ const stubParser = { parse() { } };
+
+ return assertMergedResult([
+ {
+ plugins: {
+ foo: {
+ parsers: {
+ bar: stubParser
+ }
+ }
+ },
+
+ languageOptions: {
+ parser: "foo/bar"
+ }
+ },
+ {
+ }
+ ], {
+ plugins: {
+ foo: {
+ parsers: {
+ bar: stubParser
+ }
+ },
+ ...baseConfig.plugins
+ },
+
+ languageOptions: {
+ parser: stubParser
+ }
+ });
+
+ });
+
+
+ it("should merge undefined and an object into one object", () => {
+
+ const stubParser = { parse() {} };
+
+ return assertMergedResult([
+ {
+ },
+ {
+ plugins: {
+ foo: {
+ parsers: {
+ bar: stubParser
+ }
+ }
+ },
+
+ languageOptions: {
+ parser: "foo/bar"
+ }
+ }
+ ], {
+ plugins: {
+ foo: {
+ parsers: {
+ bar: stubParser
+ }
+ },
+ ...baseConfig.plugins
+ },
+
+ languageOptions: {
+ parser: stubParser
+ }
+ });
+
+ });
+
+ });
+
+
+ describe("parserOptions", () => {
+
+ it("should error when an unexpected value is found", async () => {
+
+ await assertInvalidConfig([
+ {
+ languageOptions: {
+ parserOptions: "true"
+ }
+ }
+ ], "Expected an object.");
+ });
+
+ it("should merge two objects when second object has different keys", () => assertMergedResult([
+ {
+ languageOptions: {
+ parserOptions: {
+ foo: "whatever"
+ }
+ }
+ },
+ {
+ languageOptions: {
+ parserOptions: {
+ bar: "baz"
+ }
+ }
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ languageOptions: {
+ parserOptions: {
+ foo: "whatever",
+ bar: "baz"
+ }
+ }
+ }));
+
+ it("should deeply merge two objects when second object has different keys", () => assertMergedResult([
+ {
+ languageOptions: {
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true
+ }
+ }
+ }
+ },
+ {
+ languageOptions: {
+ parserOptions: {
+ ecmaFeatures: {
+ globalReturn: true
+ }
+ }
+ }
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ languageOptions: {
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ globalReturn: true
+ }
+ }
+ }
+ }));
+
+ it("should deeply merge two objects when second object has missing key", () => assertMergedResult([
+ {
+ languageOptions: {
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true
+ }
+ }
+ }
+ },
+ {
+ languageOptions: {
+ ecmaVersion: 2021
+ }
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ languageOptions: {
+ ecmaVersion: 2021,
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true
+ }
+ }
+ }
+ }));
+
+ it("should merge two objects when second object has overrides", () => assertMergedResult([
+ {
+ languageOptions: {
+ parserOptions: {
+ foo: "whatever"
+ }
+ }
+ },
+ {
+ languageOptions: {
+ parserOptions: {
+ foo: "bar"
+ }
+ }
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ languageOptions: {
+ parserOptions: {
+ foo: "bar"
+ }
+ }
+ }));
+
+ it("should merge an object and undefined into one object", () => assertMergedResult([
+ {
+ languageOptions: {
+ parserOptions: {
+ foo: "whatever"
+ }
+ }
+ },
+ {
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ languageOptions: {
+ parserOptions: {
+ foo: "whatever"
+ }
+ }
+ }));
+
+
+ it("should merge undefined and an object into one object", () => assertMergedResult([
+ {
+ },
+ {
+ languageOptions: {
+ parserOptions: {
+ foo: "bar"
+ }
+ }
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ languageOptions: {
+ parserOptions: {
+ foo: "bar"
+ }
+ }
+ }));
+
+
+ });
+
+
+ });
+
+ describe("rules", () => {
+
+ it("should error when an unexpected value is found", async () => {
+
+ await assertInvalidConfig([
+ {
+ rules: true
+ }
+ ], "Expected an object.");
+ });
+
+ it("should error when an invalid rule severity is set", async () => {
+
+ await assertInvalidConfig([
+ {
+ rules: {
+ foo: true
+ }
+ }
+ ], "Key \"rules\": Key \"foo\": Expected a string, number, or array.");
+ });
+
+ 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 an invalid rule severity is set in an array", async () => {
+
+ await assertInvalidConfig([
+ {
+ rules: {
+ foo: [true]
+ }
+ }
+ ], "Key \"rules\": Key \"foo\": Expected severity of \"off\", 0, \"warn\", 1, \"error\", or 2.");
+ });
+
+ it("should error when rule doesn't exist", async () => {
+
+ await assertInvalidConfig([
+ {
+ rules: {
+ foox: [1, "bar"]
+ }
+ }
+ ], /Key "rules": Key "foox": Could not find "foox" in plugin "@"./u);
+ });
+
+ it("should error and suggest alternative when rule doesn't exist", async () => {
+
+ await assertInvalidConfig([
+ {
+ rules: {
+ "test2/match": "error"
+ }
+ }
+ ], /Key "rules": Key "test2\/match": Could not find "match" in plugin "test2"\. Did you mean "test1\/match"\?/u);
+ });
+
+ it("should error when plugin for rule doesn't exist", async () => {
+
+ await assertInvalidConfig([
+ {
+ rules: {
+ "doesnt-exist/match": "error"
+ }
+ }
+ ], /Key "rules": Key "doesnt-exist\/match": Could not find plugin "doesnt-exist"\./u);
+ });
+
+ it("should error when rule options don't match schema", async () => {
+
+ await assertInvalidConfig([
+ {
+ rules: {
+ foo: [1, "bar"]
+ }
+ }
+ ], /Value "bar" should be equal to one of the allowed values/u);
+ });
+
+ it("should error when rule options don't match schema requiring at least one item", async () => {
+
+ await assertInvalidConfig([
+ {
+ rules: {
+ foo2: 1
+ }
+ }
+ ], /Value \[\] should NOT have fewer than 1 items/u);
+ });
+
+ it("should merge two objects", () => assertMergedResult([
+ {
+ rules: {
+ foo: 1,
+ bar: "error"
+ }
+ },
+ {
+ rules: {
+ baz: "warn",
+ boom: 0
+ }
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ rules: {
+ foo: [1],
+ bar: [2],
+ baz: [1],
+ boom: [0]
+ }
+ }));
+
+ it("should merge two objects when second object has simple overrides", () => assertMergedResult([
+ {
+ rules: {
+ foo: [1, "always"],
+ bar: "error"
+ }
+ },
+ {
+ rules: {
+ foo: "error",
+ bar: 0
+ }
+ }
+ ], {
+ plugins: baseConfig.plugins,
+
+ rules: {
+ foo: [2, "always"],
+ bar: [0]
+ }
+ }));
+
+ it("should merge two objects when second object has array overrides", () => assertMergedResult([
+ {
+ rules: {
+ foo: 1,
+ bar: "error"
+ }
+ },
+ {
+ rules: {
+ foo: ["error", "never"],
+ bar: ["warn", "foo"]
+ }
+ }
+ ], {
+ plugins: baseConfig.plugins,
+ rules: {
+ foo: [2, "never"],
+ bar: [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"]
+ }
+ }));
+
+ });
+
+ });
+});
const sinon = require("sinon");
const proxyquire = require("proxyquire").noCallThru().noPreserveCache();
const shell = require("shelljs");
-const { CascadingConfigArrayFactory } = require("@eslint/eslintrc/lib/cascading-config-array-factory");
+const {
+ Legacy: {
+ CascadingConfigArrayFactory
+ }
+} = require("@eslint/eslintrc");
const hash = require("../../../lib/cli-engine/hash");
const { unIndent, createCustomTeardown } = require("../../_utils");
+const coreRules = require("../../../lib/rules");
//------------------------------------------------------------------------------
// Tests
const originalDir = process.cwd();
const fixtureDir = path.resolve(fs.realpathSync(os.tmpdir()), "eslint/fixtures");
- /** @type {import("../../../lib/eslint")["ESLint"]} */
+ /** @type {import("../../../lib/eslint").ESLint} */
let ESLint;
/**
* 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
+ this.timeout(60 * 1000); // eslint-disable-line no-invalid-this -- Mocha API
shell.mkdir("-p", fixtureDir);
shell.cp("-r", "./tests/fixtures/.", fixtureDir);
});
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
+ // eslint-disable-next-line no-new -- Check for throwing
new ESLint({ ignorePath: fixtureDir });
}, new RegExp(escapeStringRegExp(`Cannot read .eslintignore file: ${fixtureDir}\nError: EISDIR: illegal operation on a directory, read`), "u"));
});
it("should not modify baseConfig when format is specified", () => {
const customBaseConfig = { root: true };
- new ESLint({ baseConfig: customBaseConfig }); // eslint-disable-line no-new
+ new ESLint({ baseConfig: customBaseConfig }); // eslint-disable-line no-new -- Check for argument side effects
assert.deepStrictEqual(customBaseConfig, { root: true });
});
"- '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 \"problem\", \"suggestion\", and \"layout\".",
+ "- '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.",
messages: [],
errorCount: 0,
warningCount: 0,
+ fatalErrorCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0,
output: "var bar = foo;",
fix: true,
fixTypes: ["layou"]
});
- }, /'fixTypes' must be an array of any of "problem", "suggestion", and "layout"\./iu);
+ }, /'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 () => {
],
errorCount: 1,
warningCount: 0,
+ fatalErrorCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0,
source: "var bar = foo",
],
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,
source: "var bar =",
],
errorCount: 1,
warningCount: 0,
+ fatalErrorCount: 1,
fixableErrorCount: 0,
fixableWarningCount: 0,
source: "var bar = foothis is a syntax error.\n return bar;",
const Module = require("module");
let originalFindPath = null;
- /* eslint-disable no-underscore-dangle */
+ /* eslint-disable no-underscore-dangle -- Override Node API */
before(() => {
originalFindPath = Module._findPath;
Module._findPath = function(id, ...otherArgs) {
after(() => {
Module._findPath = originalFindPath;
});
- /* eslint-enable no-underscore-dangle */
+ /* eslint-enable no-underscore-dangle -- Override Node API */
it("should resolve 'plugins:[\"@scope\"]' to 'node_modules/@scope/eslint-plugin'.", async () => {
eslint = new ESLint({ cwd: getFixturePath("plugin-shorthand/basic") });
it("should throw if 'options' argument contains unknown key", async () => {
eslint = new ESLint();
- await assert.rejects(() => eslint.lintText("var a = 0", { filename: "foo.js" }), /'options' must not include the unknown option 'filename'/u);
+ 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 () => {
describe("lintFiles()", () => {
- /** @type {InstanceType<import("../../../lib/eslint")["ESLint"]>} */
+ /** @type {InstanceType<import("../../../lib/eslint").ESLint>} */
let eslint;
it("should use correct parser when custom parser is specified", async () => {
messages: [],
errorCount: 0,
warningCount: 0,
+ fatalErrorCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0,
output: "true ? \"yes\" : \"no\";\n",
messages: [],
errorCount: 0,
warningCount: 0,
+ fatalErrorCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0,
usedDeprecatedRules: []
],
errorCount: 1,
warningCount: 0,
+ fatalErrorCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0,
output: "var msg = \"hi\";\nif (msg == \"hi\") {\n\n}\n",
],
errorCount: 1,
warningCount: 0,
+ fatalErrorCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0,
output: "var msg = \"hi\" + foo;\n",
assert.strictEqual(results[0].messages[0].ruleId, "test/example-rule");
});
+ it("should return two messages when executing with `baseConfig` that extends preloaded plugin config", async () => {
+ eslint = new ESLint({
+ cwd: path.join(fixtureDir, ".."),
+ useEslintrc: false,
+ baseConfig: {
+ extends: ["plugin:test/preset"]
+ },
+ plugins: {
+ test: {
+ rules: {
+ "example-rule": require("../../fixtures/rules/custom-rule")
+ },
+ configs: {
+ preset: {
+ rules: {
+ "test/example-rule": 1
+ },
+ plugins: ["test"]
+ }
+ }
+ }
+ }
+ });
+ const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]);
+
+ assert.strictEqual(results.length, 1);
+ assert.strictEqual(results[0].messages.length, 2);
+ assert.strictEqual(results[0].messages[0].ruleId, "test/example-rule");
+ });
+
it("should load plugins from the `loadPluginsRelativeTo` directory, if specified", async () => {
eslint = new ESLint({
resolvePluginsRelativeTo: getFixturePath("plugins"),
assert.throws(() => {
try {
- // eslint-disable-next-line no-new
+ // eslint-disable-next-line no-new -- Check for error
new ESLint({ cwd });
} catch (error) {
assert.strictEqual(error.messageTemplate, "failed-to-read-json");
const cwd = getFixturePath("ignored-paths", "bad-package-json-ignore");
assert.throws(() => {
- // eslint-disable-next-line no-new
+ // eslint-disable-next-line no-new -- Check for throwing
new ESLint({ cwd });
}, /Package\.json eslintIgnore property requires an array of paths/u);
});
const ignorePath = getFixturePath("ignored-paths", "not-a-directory", ".foobaz");
assert.throws(() => {
- // eslint-disable-next-line no-new
+ // eslint-disable-next-line no-new -- Check for throwing
new ESLint({ ignorePath, cwd });
}, /Cannot read \.eslintignore file/u);
});
assert.strictEqual(typeof formatter.format, "function");
});
- it("should throw if a customer formatter doesn't exist", async () => {
+ it("should throw if a custom formatter doesn't exist", async () => {
const engine = new ESLint();
const formatterPath = getFixturePath("formatters", "doesntexist.js");
const fullFormatterPath = path.resolve(formatterPath);
});
});
+ describe("getRulesMetaForResults()", () => {
+ it("should return empty object when there are no linting errors", async () => {
+ const engine = new ESLint({
+ useEslintrc: false
+ });
+
+ const rulesMeta = engine.getRulesMetaForResults([]);
+
+ assert.strictEqual(Object.keys(rulesMeta).length, 0);
+ });
+
+ it("should return one rule meta when there is a linting error", async () => {
+ const engine = new ESLint({
+ useEslintrc: false,
+ overrideConfig: {
+ rules: {
+ semi: 2
+ }
+ }
+ });
+
+ const results = await engine.lintText("a");
+ const rulesMeta = engine.getRulesMetaForResults(results);
+
+ assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta);
+ });
+
+ it("should return multiple rule meta when there are multiple linting errors", async () => {
+ const engine = new ESLint({
+ useEslintrc: false,
+ overrideConfig: {
+ rules: {
+ semi: 2,
+ quotes: [2, "double"]
+ }
+ }
+ });
+
+ const results = await engine.lintText("'a'");
+ const rulesMeta = engine.getRulesMetaForResults(results);
+
+ assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta);
+ assert.strictEqual(rulesMeta.quotes, coreRules.get("quotes").meta);
+ });
+
+ it("should return multiple rule meta when there are multiple linting errors from a plugin", async () => {
+ const nodePlugin = require("eslint-plugin-node");
+ const engine = new ESLint({
+ useEslintrc: false,
+ plugins: {
+ node: nodePlugin
+ },
+ overrideConfig: {
+ plugins: ["node"],
+ rules: {
+ "node/no-new-require": 2,
+ semi: 2,
+ quotes: [2, "double"]
+ }
+ }
+ });
+
+ const results = await engine.lintText("new require('hi')");
+ const rulesMeta = engine.getRulesMetaForResults(results);
+
+ assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta);
+ assert.strictEqual(rulesMeta.quotes, coreRules.get("quotes").meta);
+ assert.strictEqual(
+ rulesMeta["node/no-new-require"],
+ nodePlugin.rules["no-new-require"].meta
+ );
+ });
+ });
+
describe("outputFixes()", () => {
afterEach(() => {
sinon.verifyAndRestore();
].join("\n");
const config = {
ignore: true,
+ useEslintrc: false,
allowInlineConfig: false,
overrideConfig: {
env: { browser: true },
].join("\n");
const config = {
ignore: true,
+ useEslintrc: false,
allowInlineConfig: true,
overrideConfig: {
env: { browser: true },
message: "Unused eslint-disable directive (no problems were reported).",
line: 1,
column: 1,
+ fix: {
+ range: [0, 20],
+ text: " "
+ },
severity: 2,
nodeType: null
}
],
errorCount: 1,
warningCount: 0,
- fixableErrorCount: 0,
+ fatalErrorCount: 0,
+ fixableErrorCount: 1,
fixableWarningCount: 0,
source: "/* eslint-disable */",
usedDeprecatedRules: []
],
source: "a == b",
usedDeprecatedRules: [],
- warningCount: 0
+ warningCount: 0,
+ fatalErrorCount: 0
}
]);
});
fixableWarningCount: 0,
messages: [],
usedDeprecatedRules: [],
- warningCount: 0
+ warningCount: 0,
+ fatalErrorCount: 0
}
]);
});
fixableWarningCount: 0,
messages: [],
usedDeprecatedRules: [],
- warningCount: 0
+ warningCount: 0,
+ fatalErrorCount: 0
}
]);
});
],
source: "a == b",
usedDeprecatedRules: [],
- warningCount: 0
+ warningCount: 0,
+ fatalErrorCount: 0
}
]);
});
[
["JavaScript", "foo.js", espree.parse],
["JSON", "bar.json", JSON.parse],
- ["YAML", "foo.yaml", yaml.safeLoad],
- ["YML", "foo.yml", yaml.safeLoad]
+ ["YAML", "foo.yaml", yaml.load],
+ ["YML", "foo.yml", yaml.load]
].forEach(([fileType, filename, validate]) => {
it(`should write a file through fs when a ${fileType} path is passed`, () => {
"use strict";
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
const assert = require("chai").assert;
const applyDisableDirectives = require("../../../lib/linter/apply-disable-directives");
+//-----------------------------------------------------------------------------
+// Helpers
+//-----------------------------------------------------------------------------
+
+/**
+ * Creates a ParentComment for a given range.
+ * @param {[number, number]} range total range of the comment
+ * @param {string} value String value of the comment
+ * @param {string[]} ruleIds Rule IDs reported in the value
+ * @returns {ParentComment} Test-ready ParentComment object.
+ */
+function createParentComment(range, value, ruleIds = []) {
+ return {
+ commentToken: { range, value },
+ ruleIds
+ };
+}
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
describe("apply-disable-directives", () => {
describe("/* eslint-disable */ comments without rules", () => {
it("keeps problems before the comment on the same line", () => {
assert.deepStrictEqual(
applyDisableDirectives({
- directives: [{ type: "disable", line: 1, column: 8, ruleId: null }],
+ directives: [{ parentComment: createParentComment([0, 7]), type: "disable", line: 1, column: 8, ruleId: null }],
problems: [{ line: 1, column: 7, ruleId: "foo" }]
}),
[{ ruleId: "foo", line: 1, column: 7 }]
it("keeps problems on a previous line before the comment", () => {
assert.deepStrictEqual(
applyDisableDirectives({
- directives: [{ type: "disable", line: 2, column: 8, ruleId: null }],
+ directives: [{ parentComment: createParentComment([21, 27]), type: "disable", line: 2, column: 1, ruleId: null }],
problems: [{ line: 1, column: 10, ruleId: "foo" }]
}),
[{ ruleId: "foo", line: 1, column: 10 }]
it("keeps problems after the comment that have a different ruleId", () => {
assert.deepStrictEqual(
applyDisableDirectives({
- directives: [{ type: "disable", line: 1, column: 8, ruleId: "foo" }],
+ directives: [{
+ parentComment: createParentComment([26, 29]),
+ type: "disable",
+ line: 1,
+ column: 1,
+ ruleId: "foo"
+ }],
problems: [{ line: 2, column: 3, ruleId: "not-foo" }]
}),
[{ line: 2, column: 3, ruleId: "not-foo" }]
it("keeps problems before the comment that have the same ruleId", () => {
assert.deepStrictEqual(
applyDisableDirectives({
- directives: [{ type: "disable", line: 1, column: 8, ruleId: "foo" }],
+ directives: [{
+ parentComment: createParentComment([7, 31]),
+ type: "disable",
+ line: 1,
+ column: 8,
+ ruleId: "foo"
+ }],
problems: [{ line: 1, column: 7, ruleId: "foo" }]
}),
[{ line: 1, column: 7, ruleId: "foo" }]
assert.deepStrictEqual(
applyDisableDirectives({
directives: [
- { type: "disable", line: 1, column: 1, ruleId: null },
- { type: "enable", line: 1, column: 5, ruleId: null }
+ {
+ parentComment: createParentComment([0, 26]),
+ type: "disable",
+ line: 1,
+ column: 1,
+ ruleId: null
+ },
+ {
+ parentComment: createParentComment([27, 45]),
+ type: "enable",
+ line: 1,
+ column: 26,
+ ruleId: null
+ }
],
- problems: [{ line: 1, column: 7, ruleId: "foo" }]
+ problems: [{ line: 1, column: 27, ruleId: "foo" }]
}),
- [{ line: 1, column: 7, ruleId: "foo" }]
+ [{ line: 1, column: 27, ruleId: "foo" }]
);
});
assert.deepStrictEqual(
applyDisableDirectives({
directives: [
- { type: "disable", line: 1, column: 1, ruleId: null },
- { type: "enable", line: 1, column: 5, ruleId: null }
+ {
+ parentComment: createParentComment([0, 25]),
+ type: "disable",
+ line: 1,
+ column: 1,
+ ruleId: null
+ },
+ {
+ parentComment: createParentComment([26, 40]),
+ type: "enable",
+ line: 1,
+ column: 26,
+ ruleId: null
+ }
],
- problems: [{ line: 1, column: 5, ruleId: "foo" }]
+ problems: [{ line: 1, column: 26, ruleId: "foo" }]
}),
- [{ line: 1, column: 5, ruleId: "foo" }]
+ [{ line: 1, column: 26, ruleId: "foo" }]
);
});
applyDisableDirectives({
directives: [
{ type: "disable", line: 1, column: 1, ruleId: null },
- { type: "enable", line: 1, column: 5, ruleId: null }
+ { type: "enable", line: 1, column: 26, ruleId: null }
],
problems: [{ line: 1, column: 3, ruleId: "foo" }]
}),
assert.deepStrictEqual(
applyDisableDirectives({
directives: [
- { type: "disable", line: 1, column: 1, ruleId: null },
- { type: "enable", line: 1, column: 5, ruleId: "foo" },
- { type: "disable", line: 2, column: 1, ruleId: "foo" }
+ {
+ parentComment: createParentComment([0, 20]),
+ type: "disable",
+ line: 1,
+ column: 1,
+ ruleId: null
+ },
+ {
+ parentComment: createParentComment([26, 44]),
+ type: "enable",
+ line: 1,
+ column: 26,
+ ruleId: "foo"
+ },
+ {
+ parentComment: createParentComment([45, 63]),
+ type: "disable",
+ line: 2,
+ column: 1,
+ ruleId: "foo"
+ }
],
problems: [{ line: 3, column: 3, ruleId: "foo" }]
}),
assert.deepStrictEqual(
applyDisableDirectives({
directives: [
- { type: "disable", line: 1, column: 1, ruleId: null },
- { type: "enable", line: 1, column: 5, ruleId: "foo" },
- { type: "disable", line: 2, column: 1, ruleId: null }
+ {
+ parentComment: createParentComment([0, 20]),
+ type: "disable",
+ line: 1,
+ column: 1,
+ ruleId: null
+ },
+ {
+ parentComment: createParentComment([21, 44]),
+ type: "enable",
+ line: 1,
+ column: 26,
+ ruleId: "foo"
+ },
+ {
+ parentComment: createParentComment([45, 63]),
+ type: "disable",
+ line: 2,
+ column: 1,
+ ruleId: null
+ }
],
problems: [{ line: 3, column: 3, ruleId: "foo" }]
}),
assert.deepStrictEqual(
applyDisableDirectives({
directives: [
- { type: "disable", line: 1, column: 1, ruleId: "foo" },
- { type: "enable", line: 1, column: 5, ruleId: null }
+ {
+ parentComment: createParentComment([0, 20]),
+ type: "disable",
+ line: 1,
+ column: 1,
+ ruleId: "foo"
+ },
+ {
+ parentComment: createParentComment([25, 44]),
+ type: "enable",
+ line: 1,
+ column: 26,
+ ruleId: null
+ }
],
problems: [{ line: 1, column: 3, ruleId: "not-foo" }]
}),
assert.deepStrictEqual(
applyDisableDirectives({
directives: [
- { type: "disable", line: 1, column: 4, ruleId: null },
- { type: "enable", line: 2, column: 1, ruleId: "foo" }
+ {
+ parentComment: createParentComment([0, 20]),
+ type: "disable",
+ line: 1,
+ column: 1,
+ ruleId: null
+ },
+ {
+ parentComment: createParentComment([21, 44]),
+ type: "enable",
+ line: 2,
+ column: 1,
+ ruleId: "foo"
+ }
],
problems: [{ line: 2, column: 4, ruleId: "foo" }]
}),
assert.deepStrictEqual(
applyDisableDirectives({
directives: [
- { type: "disable", line: 1, column: 4, ruleId: null },
- { type: "enable", line: 2, column: 1, ruleId: "foo" }
+ {
+ parentComment: createParentComment([0, 20]),
+ type: "disable",
+ line: 1,
+ column: 1,
+ ruleId: null
+ },
+ {
+ parentComment: createParentComment([21, 44]),
+ type: "enable",
+ line: 2,
+ column: 1,
+ ruleId: "foo"
+ }
],
problems: [{ line: 2, column: 1, ruleId: "foo" }]
}),
assert.deepStrictEqual(
applyDisableDirectives({
directives: [
- { type: "disable", line: 1, column: 4, ruleId: null },
+ { type: "disable", line: 1, column: 1, ruleId: null },
{ type: "enable", line: 2, column: 1, ruleId: "foo" }
],
problems: [{ line: 2, column: 4, ruleId: "not-foo" }]
applyDisableDirectives({
directives: [
{ type: "disable", line: 1, column: 1, ruleId: null },
- { type: "enable", line: 1, column: 3, ruleId: "foo" },
- { type: "enable", line: 1, column: 5, ruleId: "bar" }
+ { type: "enable", line: 1, column: 22, ruleId: "foo" },
+ { type: "enable", line: 1, column: 46, ruleId: "bar" }
],
problems: [
- { line: 1, column: 2, ruleId: "foo" },
- { line: 1, column: 2, ruleId: "bar" },
- { line: 1, column: 4, ruleId: "foo" },
- { line: 1, column: 4, ruleId: "bar" },
- { line: 1, column: 6, ruleId: "foo" },
- { line: 1, column: 6, ruleId: "bar" }
+ { 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: 4, ruleId: "foo" },
- { line: 1, column: 6, ruleId: "foo" },
- { line: 1, column: 6, ruleId: "bar" }
+ { line: 1, column: 30, ruleId: "foo" },
+ { line: 1, column: 50, ruleId: "foo" },
+ { line: 1, column: 50, ruleId: "bar" }
]
);
});
it("keeps problems on a previous line", () => {
assert.deepStrictEqual(
applyDisableDirectives({
- directives: [{ type: "disable-line", line: 2, column: 1, ruleId: null }],
+ directives: [{
+ parentComment: createParentComment([6, 27]),
+ type: "disable-line",
+ line: 2,
+ column: 1,
+ ruleId: null
+ }],
problems: [{ line: 1, column: 5, ruleId: "foo" }]
}),
[{ line: 1, column: 5, ruleId: "foo" }]
it("filters problems before the comment on the same line", () => {
assert.deepStrictEqual(
applyDisableDirectives({
- directives: [{ type: "disable-line", line: 1, column: 5, ruleId: null }],
+ directives: [{
+ parentComment: createParentComment([7, 28]),
+ type: "disable-line",
+ line: 1,
+ column: 8,
+ ruleId: null
+ }],
problems: [{ line: 1, column: 1, ruleId: "foo" }]
}),
[]
it("filters problems after the comment on the same line", () => {
assert.deepStrictEqual(
applyDisableDirectives({
- directives: [{ type: "disable-line", line: 1, column: 5, ruleId: null }],
+ directives: [{
+ parentComment: createParentComment([7, 28]),
+ type: "disable-line",
+ line: 1,
+ column: 8,
+ ruleId: null
+ }],
problems: [{ line: 1, column: 10, ruleId: "foo" }]
}),
[]
it("keeps problems on a following line", () => {
assert.deepStrictEqual(
applyDisableDirectives({
- directives: [{ type: "disable-line", line: 1, column: 4 }],
+ directives: [{
+ parentComment: createParentComment([7, 34]),
+ type: "disable-line",
+ line: 1,
+ column: 8,
+ ruleId: "foo"
+ }],
problems: [{ line: 2, column: 1, ruleId: "foo" }]
}),
[{ line: 2, column: 1, ruleId: "foo" }]
it("filters problems on the current line that match the ruleId", () => {
assert.deepStrictEqual(
applyDisableDirectives({
- directives: [{ type: "disable-line", line: 1, column: 4, ruleId: "foo" }],
+ directives: [{
+ parentComment: createParentComment([7, 34]),
+ type: "disable-line",
+ line: 1,
+ column: 8,
+ ruleId: "foo"
+ }],
problems: [{ line: 1, column: 2, ruleId: "foo" }]
}),
[]
it("keeps problems on the current line that do not match the ruleId", () => {
assert.deepStrictEqual(
applyDisableDirectives({
- directives: [{ type: "disable-line", line: 1, column: 4, ruleId: "foo" }],
+ directives: [{ parentComment: createParentComment([0, 27]), type: "disable-line", line: 1, column: 1, ruleId: "foo" }],
problems: [{ line: 1, column: 2, ruleId: "not-foo" }]
}),
[{ line: 1, column: 2, ruleId: "not-foo" }]
assert.deepStrictEqual(
applyDisableDirectives({
directives: [
- { type: "disable", line: 1, column: 1, ruleId: null },
- { type: "disable-line", line: 1, column: 3, ruleId: "foo" }
+ {
+ parentComment: createParentComment([0, 21]),
+ type: "disable",
+ line: 1,
+ column: 1,
+ ruleId: null
+ },
+ {
+ parentComment: createParentComment([24, 28]),
+ type: "disable-line",
+ line: 1,
+ column: 22,
+ ruleId: "foo"
+ }
],
problems: [{ line: 1, column: 5, ruleId: "not-foo" }]
}),
assert.deepStrictEqual(
applyDisableDirectives({
directives: [
- { type: "disable-line", line: 1, column: 5, ruleId: "foo" },
- { type: "disable-line", line: 2, column: 5, ruleId: "foo" },
- { type: "disable-line", line: 3, column: 5, ruleId: "foo" },
- { type: "disable-line", line: 4, column: 5, ruleId: "foo" },
- { type: "disable-line", line: 5, column: 5, ruleId: "foo" },
- { type: "disable-line", line: 6, column: 5, ruleId: "foo" }
+ {
+ parentComment: createParentComment([7, 34]),
+ type: "disable-line",
+ line: 1,
+ column: 8,
+ ruleId: "foo"
+ },
+ {
+ parentComment: createParentComment([38, 73]),
+ type: "disable-line",
+ line: 2,
+ column: 8,
+ ruleId: "foo"
+ },
+ {
+ parentComment: createParentComment([76, 111]),
+ type: "disable-line",
+ line: 3,
+ column: 8,
+ ruleId: "foo"
+ },
+ {
+ parentComment: createParentComment([114, 149]),
+ type: "disable-line",
+ line: 4,
+ column: 8,
+ ruleId: "foo"
+ },
+ {
+ parentComment: createParentComment([152, 187]),
+ type: "disable-line",
+ line: 5,
+ column: 8,
+ ruleId: "foo"
+ },
+ {
+ parentComment: createParentComment([190, 225]),
+ type: "disable-line",
+ line: 6,
+ column: 8,
+ ruleId: "foo"
+ }
],
problems: [{ line: 2, column: 1, ruleId: "foo" }]
}),
it("filters problems on the next line", () => {
assert.deepStrictEqual(
applyDisableDirectives({
- directives: [{ type: "disable-next-line", line: 1, column: 1, ruleId: null }],
+ directives: [{
+ parentComment: createParentComment([0, 31]),
+ type: "disable-next-line",
+ line: 1,
+ column: 1,
+ ruleId: null
+ }],
problems: [{ line: 2, column: 3, ruleId: "foo" }]
}),
[]
it("keeps problems on the same line", () => {
assert.deepStrictEqual(
applyDisableDirectives({
- directives: [{ type: "disable-next-line", line: 1, column: 1, ruleId: null }],
+ directives: [{
+ parentComment: createParentComment([0, 31]),
+ type: "disable-next-line",
+ line: 1,
+ column: 1,
+ ruleId: null
+ }],
problems: [{ line: 1, column: 3, ruleId: "foo" }]
}),
[{ line: 1, column: 3, ruleId: "foo" }]
it("keeps problems after the next line", () => {
assert.deepStrictEqual(
applyDisableDirectives({
- directives: [{ type: "disable-next-line", line: 1, column: 1, ruleId: null }],
+ directives: [{
+ parentComment: createParentComment([0, 31]),
+ type: "disable-next-line",
+ line: 1,
+ column: 1,
+ ruleId: null
+ }],
problems: [{ line: 3, column: 3, ruleId: "foo" }]
}),
[{ line: 3, column: 3, ruleId: "foo" }]
it("keeps problems on the next line that do not match the ruleId", () => {
assert.deepStrictEqual(
applyDisableDirectives({
- directives: [{ type: "disable-next-line", line: 1, column: 1, ruleId: "foo" }],
+ directives: [{
+ parentComment: createParentComment([0, 31]),
+ type: "disable-next-line",
+ line: 1,
+ column: 1,
+ ruleId: "foo"
+ }],
problems: [{ line: 2, column: 1, ruleId: "not-foo" }]
}),
[{ line: 2, column: 1, ruleId: "not-foo" }]
it("Adds a problem for /* eslint-disable */", () => {
assert.deepStrictEqual(
applyDisableDirectives({
- directives: [{ type: "disable", line: 1, column: 5 }],
+ directives: [{
+ parentComment: createParentComment([0, 20]),
+ type: "disable",
+ line: 1,
+ column: 1
+ }],
problems: [],
reportUnusedDisableDirectives: "error"
}),
ruleId: null,
message: "Unused eslint-disable directive (no problems were reported).",
line: 1,
- column: 5,
+ column: 1,
+ fix: {
+ range: [0, 20],
+ text: " "
+ },
+ severity: 2,
+ nodeType: null
+ }
+ ]
+ );
+ });
+
+ it("Does not fix a problem for /* eslint-disable */ when disableFixes is enabled", () => {
+ assert.deepStrictEqual(
+ applyDisableDirectives({
+ directives: [{
+ parentComment: createParentComment([0, 20]),
+ type: "disable",
+ line: 1,
+ column: 1
+ }],
+ disableFixes: true,
+ problems: [],
+ reportUnusedDisableDirectives: "error"
+ }),
+ [
+ {
+ ruleId: null,
+ message: "Unused eslint-disable directive (no problems were reported).",
+ line: 1,
+ column: 1,
severity: 2,
nodeType: null
}
it("Does not add a problem for /* eslint-disable */ /* (problem) */", () => {
assert.deepStrictEqual(
applyDisableDirectives({
- directives: [{ type: "disable", line: 1, column: 5, ruleId: null }],
+ directives: [{ type: "disable", line: 1, column: 1, ruleId: null }],
problems: [{ line: 2, column: 1, ruleId: "foo" }],
reportUnusedDisableDirectives: "error"
}),
it("Adds a problem for /* eslint-disable foo */", () => {
assert.deepStrictEqual(
applyDisableDirectives({
- directives: [{ type: "disable", line: 1, column: 5, ruleId: "foo" }],
+ directives: [{
+ parentComment: createParentComment([0, 21]),
+ type: "disable",
+ line: 1,
+ column: 1,
+ ruleId: "foo"
+ }],
problems: [],
reportUnusedDisableDirectives: "error"
}),
ruleId: null,
message: "Unused eslint-disable directive (no problems were reported from 'foo').",
line: 1,
- column: 5,
+ column: 1,
+ fix: {
+ range: [0, 21],
+ text: " "
+ },
severity: 2,
nodeType: null
}
it("Adds a problem for /* eslint-disable foo */ /* (problem from another rule) */", () => {
assert.deepStrictEqual(
applyDisableDirectives({
- directives: [{ type: "disable", line: 1, column: 5, ruleId: "foo" }],
+ directives: [{
+ parentComment: createParentComment([0, 24]),
+ type: "disable",
+ line: 1,
+ column: 1,
+ ruleId: "foo"
+ }],
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: 5,
+ column: 1,
+ fix: {
+ range: [0, 24],
+ text: " "
+ },
severity: 2,
nodeType: null
},
assert.deepStrictEqual(
applyDisableDirectives({
directives: [
- { type: "disable", line: 1, column: 5, ruleId: null },
- { type: "enable", line: 1, column: 6, ruleId: "foo" }
+ {
+ parentComment: createParentComment([0, 21]),
+ type: "disable",
+ line: 1,
+ column: 8,
+ ruleId: null
+ },
+ {
+ parentComment: createParentComment([0, 21]),
+ type: "enable",
+ line: 1,
+ column: 24,
+ ruleId: "foo"
+ }
],
problems: [{ line: 1, column: 2, ruleId: "foo" }],
reportUnusedDisableDirectives: "error"
{
ruleId: null,
message: "Unused eslint-disable directive (no problems were reported).",
+ fix: {
+ range: [0, 21],
+ text: " "
+ },
line: 1,
- column: 5,
+ column: 8,
severity: 2,
nodeType: null
}
assert.deepStrictEqual(
applyDisableDirectives({
directives: [
- { type: "disable", line: 1, column: 5, ruleId: null },
- { type: "enable", line: 1, column: 6, ruleId: null }
+ {
+ parentComment: createParentComment([0, 20]),
+ type: "disable",
+ line: 1,
+ column: 1,
+ ruleId: null
+ },
+ {
+ parentComment: createParentComment([21, 41]),
+ type: "enable",
+ line: 1,
+ column: 12,
+ ruleId: null
+ }
],
problems: [],
reportUnusedDisableDirectives: "error"
ruleId: null,
message: "Unused eslint-disable directive (no problems were reported).",
line: 1,
- column: 5,
+ column: 1,
+ fix: {
+ range: [0, 20],
+ text: " "
+ },
severity: 2,
nodeType: null
}
assert.deepStrictEqual(
applyDisableDirectives({
directives: [
- { type: "disable", line: 1, column: 1, ruleId: null },
- { type: "disable", line: 2, column: 1, ruleId: null }
+ {
+ parentComment: createParentComment([0, 21]),
+ type: "disable",
+ line: 1,
+ column: 1,
+ ruleId: null
+ },
+ {
+ parentComment: createParentComment([21, 42]),
+ type: "disable",
+ line: 2,
+ column: 1,
+ ruleId: null
+ }
],
problems: [],
reportUnusedDisableDirectives: "error"
message: "Unused eslint-disable directive (no problems were reported).",
line: 1,
column: 1,
+ fix: {
+ range: [0, 21],
+ text: " "
+ },
severity: 2,
nodeType: null
},
message: "Unused eslint-disable directive (no problems were reported).",
line: 2,
column: 1,
+ fix: {
+ range: [21, 42],
+ text: " "
+ },
severity: 2,
nodeType: null
}
assert.deepStrictEqual(
applyDisableDirectives({
directives: [
- { type: "disable", line: 1, column: 1, ruleId: null },
- { type: "disable", line: 2, column: 1, ruleId: null }
+ {
+ parentComment: createParentComment([0, 21]),
+ type: "disable",
+ line: 1,
+ column: 1,
+ ruleId: null
+ },
+ {
+ parentComment: createParentComment([22, 45]),
+ type: "disable",
+ line: 2,
+ column: 1,
+ ruleId: null
+ }
],
problems: [{ line: 3, column: 1, ruleId: "foo" }],
reportUnusedDisableDirectives: "error"
message: "Unused eslint-disable directive (no problems were reported).",
line: 1,
column: 1,
+ fix: {
+ range: [0, 21],
+ text: " "
+ },
severity: 2,
nodeType: null
}
assert.deepStrictEqual(
applyDisableDirectives({
directives: [
- { type: "disable", line: 1, column: 1, ruleId: "foo" },
- { type: "disable", line: 2, column: 1, ruleId: null }
+ {
+ parentComment: createParentComment([0, 21]),
+ type: "disable",
+ line: 1,
+ column: 1,
+ ruleId: "foo"
+ },
+ {
+ parentComment: createParentComment([22, 45]),
+ type: "disable",
+ line: 2,
+ column: 1,
+ ruleId: null
+ }
],
problems: [{ line: 3, column: 1, ruleId: "foo" }],
reportUnusedDisableDirectives: "error"
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("Does not add a problem for /* eslint-disable foo */ /* (problem from foo) */", () => {
assert.deepStrictEqual(
applyDisableDirectives({
- directives: [{ type: "disable", line: 1, column: 5, ruleId: "foo" }],
+ directives: [{ type: "disable", line: 1, column: 1, ruleId: "foo" }],
problems: [{ line: 1, column: 6, ruleId: "foo" }],
reportUnusedDisableDirectives: "error"
}),
assert.deepStrictEqual(
applyDisableDirectives({
directives: [
- { type: "disable", line: 1, column: 1, ruleId: null },
- { type: "disable", line: 2, column: 1, ruleId: "foo" }
+ {
+ parentComment: createParentComment([0, 21]),
+ type: "disable",
+ line: 1,
+ column: 1,
+ ruleId: null
+ },
+ {
+ parentComment: createParentComment([22, 45]),
+ type: "disable",
+ line: 2,
+ column: 1,
+ ruleId: "foo"
+ }
],
problems: [{ line: 3, column: 1, ruleId: "foo" }],
reportUnusedDisableDirectives: "error"
message: "Unused eslint-disable directive (no problems were reported).",
line: 1,
column: 1,
+ fix: {
+ range: [0, 21],
+ text: " "
+ },
severity: 2,
nodeType: null
}
assert.deepStrictEqual(
applyDisableDirectives({
directives: [
- { type: "disable", line: 1, column: 1, ruleId: null },
- { type: "disable", line: 2, column: 1, ruleId: "foo" }
+ {
+ parentComment: createParentComment([0, 20]),
+ type: "disable",
+ line: 1,
+ column: 1,
+ ruleId: null
+ },
+ {
+ parentComment: createParentComment([21, 45]),
+ type: "disable",
+ line: 2,
+ column: 1,
+ ruleId: "foo"
+ }
],
problems: [{ line: 3, column: 1, ruleId: "bar" }],
reportUnusedDisableDirectives: "error"
message: "Unused eslint-disable directive (no problems were reported from 'foo').",
line: 2,
column: 1,
+ fix: {
+ range: [21, 45],
+ text: " "
+ },
severity: 2,
nodeType: null
}
assert.deepStrictEqual(
applyDisableDirectives({
directives: [
- { type: "disable", line: 1, column: 5, ruleId: "foo" },
- { type: "enable", line: 1, column: 8, ruleId: "foo" }
+ {
+ parentComment: createParentComment([0, 20]),
+ type: "disable",
+ line: 1,
+ column: 1,
+ ruleId: "foo"
+ },
+ {
+ parentComment: createParentComment([25, 46]),
+ type: "enable",
+ line: 1,
+ column: 26,
+ ruleId: "foo"
+ }
],
- problems: [{ line: 1, column: 10, ruleId: "foo" }],
+ 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: 5,
+ column: 1,
+ fix: {
+ range: [0, 20],
+ text: " "
+ },
severity: 2,
nodeType: null
},
{
ruleId: "foo",
line: 1,
- column: 10
+ column: 30
}
]
);
assert.deepStrictEqual(
applyDisableDirectives({
directives: [
- { type: "disable", line: 1, column: 5, ruleId: "foo" },
- { type: "enable", line: 1, column: 8, ruleId: null }
+ {
+ parentComment: createParentComment([0, 24]),
+ type: "disable",
+ line: 1,
+ column: 1,
+ ruleId: "foo"
+ },
+ {
+ parentComment: createParentComment([25, 49]),
+ type: "enable",
+ line: 1,
+ column: 26,
+ ruleId: null
+ }
],
- problems: [{ line: 1, column: 10, ruleId: "foo" }],
+ 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: 5,
+ column: 1,
+ fix: {
+ range: [0, 24],
+ text: " "
+ },
severity: 2,
nodeType: null
},
{
ruleId: "foo",
line: 1,
- column: 10
+ column: 30
}
]
);
assert.deepStrictEqual(
applyDisableDirectives({
directives: [
- { type: "disable", line: 1, column: 1, ruleId: null },
- { type: "disable", line: 2, column: 1, ruleId: "foo" },
- { type: "enable", line: 3, column: 1, ruleId: "foo" }
+ {
+ parentComment: createParentComment([0, 21]),
+ type: "disable",
+ line: 1,
+ column: 1,
+ ruleId: null
+ },
+ {
+ parentComment: createParentComment([22, 45]),
+ type: "disable",
+ line: 2,
+ column: 1,
+ ruleId: "foo"
+ },
+ {
+ parentComment: createParentComment([46, 69]),
+ type: "enable",
+ line: 3,
+ column: 1,
+ ruleId: "foo"
+ }
],
problems: [{ line: 4, column: 1, ruleId: "foo" }],
reportUnusedDisableDirectives: "error"
message: "Unused eslint-disable directive (no problems were reported).",
line: 1,
column: 1,
+ fix: {
+ range: [0, 21],
+ text: " "
+ },
severity: 2,
nodeType: 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
},
{
it("Adds a problem for // eslint-disable-line", () => {
assert.deepStrictEqual(
applyDisableDirectives({
- directives: [{ type: "disable-line", line: 1, column: 5, ruleId: null }],
+ directives: [{
+ parentComment: createParentComment([0, 22]),
+ type: "disable-line",
+ line: 1,
+ column: 1,
+ ruleId: null
+ }],
problems: [],
reportUnusedDisableDirectives: "error"
}),
ruleId: null,
message: "Unused eslint-disable directive (no problems were reported).",
line: 1,
- column: 5,
+ column: 1,
+ fix: {
+ range: [0, 22],
+ text: " "
+ },
severity: 2,
nodeType: null
}
it("Does not add a problem for // eslint-disable-line (problem)", () => {
assert.deepStrictEqual(
applyDisableDirectives({
- directives: [{ type: "disable-line", line: 1, column: 5, ruleId: null }],
+ directives: [{ type: "disable-line", line: 1, column: 1, ruleId: null }],
problems: [{ line: 1, column: 10, ruleId: "foo" }],
reportUnusedDisableDirectives: "error"
}),
it("Adds a problem for // eslint-disable-next-line", () => {
assert.deepStrictEqual(
applyDisableDirectives({
- directives: [{ type: "disable-next-line", line: 1, column: 5, ruleId: null }],
+ directives: [{
+ parentComment: createParentComment([0, 27]),
+ type: "disable-next-line",
+ line: 1,
+ column: 1,
+ ruleId: null
+ }],
problems: [],
reportUnusedDisableDirectives: "error"
}),
ruleId: null,
message: "Unused eslint-disable directive (no problems were reported).",
line: 1,
- column: 5,
+ column: 1,
+ fix: {
+ range: [0, 27],
+ text: " "
+ },
severity: 2,
nodeType: null
}
it("Does not add a problem for // eslint-disable-next-line \\n (problem)", () => {
assert.deepStrictEqual(
applyDisableDirectives({
- directives: [{ type: "disable-next-line", line: 1, column: 5, ruleId: null }],
+ directives: [{ type: "disable-next-line", line: 1, column: 1, ruleId: null }],
problems: [{ line: 2, column: 10, ruleId: "foo" }],
reportUnusedDisableDirectives: "error"
}),
assert.deepStrictEqual(
applyDisableDirectives({
directives: [
- { type: "disable", line: 1, column: 1, ruleId: null },
- { type: "disable-line", line: 1, column: 5, ruleId: null }
+ { parentComment: createParentComment([0, 20]), type: "disable", line: 1, column: 1, ruleId: null },
+ { parentComment: createParentComment([20, 43]), type: "disable-line", line: 1, column: 22, ruleId: null }
],
problems: [],
reportUnusedDisableDirectives: "error"
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: 5,
+ column: 22,
+ fix: {
+ range: [20, 43],
+ text: " "
+ },
severity: 2,
nodeType: null
}
it("Does not add problems when reportUnusedDisableDirectives: \"off\" is used", () => {
assert.deepStrictEqual(
applyDisableDirectives({
- directives: [{ type: "disable-next-line", line: 1, column: 5, ruleId: null }],
+ directives: [{ parentComment: createParentComment([0, 27]), type: "disable-next-line", line: 1, column: 1, ruleId: null }],
problems: [],
reportUnusedDisableDirectives: "off"
}),
);
});
});
+
+ describe("unused rules within directives", () => {
+ it("Adds a problem for /* eslint-disable used, unused */", () => {
+ const parentComment = createParentComment([0, 32], " eslint-disable used, unused ", ["used", "unused"]);
+
+ assert.deepStrictEqual(
+ applyDisableDirectives({
+ directives: [
+ {
+ parentComment,
+ ruleId: "used",
+ type: "disable",
+ line: 1,
+ column: 18
+ },
+ {
+ parentComment,
+ ruleId: "unused",
+ type: "disable",
+ line: 1,
+ column: 22
+ }
+ ],
+ 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
+ }
+ ]
+ );
+ });
+ it("Adds a problem for /* eslint-disable used , unused , -- unused and used are ok */", () => {
+ const parentComment = createParentComment([0, 62], " eslint-disable used , unused , -- unused and used are ok ", ["used", "unused"]);
+
+ assert.deepStrictEqual(
+ applyDisableDirectives({
+ directives: [
+ {
+ parentComment,
+ ruleId: "used",
+ type: "disable",
+ line: 1,
+ column: 18
+ },
+ {
+ parentComment,
+ ruleId: "unused",
+ type: "disable",
+ line: 1,
+ column: 24
+ }
+ ],
+ 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
+ }
+ ]
+ );
+ });
+
+ it("Adds a problem for /* eslint-disable unused, used */", () => {
+ const parentComment = createParentComment([0, 32], " eslint-disable unused, used ", ["unused", "used"]);
+
+ assert.deepStrictEqual(
+ applyDisableDirectives({
+ directives: [
+ {
+ parentComment,
+ ruleId: "unused",
+ type: "disable",
+ line: 1,
+ column: 18
+ },
+ {
+ parentComment,
+ ruleId: "used",
+ type: "disable",
+ line: 1,
+ column: 25
+ }
+ ],
+ 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
+ }
+ ]
+ );
+ });
+
+ it("Adds a problem for /* eslint-disable unused,, ,, used */", () => {
+ const parentComment = createParentComment([0, 37], " eslint-disable unused,, ,, used ", ["unused", "used"]);
+
+ assert.deepStrictEqual(
+ applyDisableDirectives({
+ directives: [
+ {
+ parentComment,
+ ruleId: "unused",
+ type: "disable",
+ line: 1,
+ column: 18
+ },
+ {
+ parentComment,
+ ruleId: "used",
+ type: "disable",
+ line: 1,
+ column: 29
+ }
+ ],
+ 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
+ }
+ ]
+ );
+ });
+
+ it("Adds a problem for /* eslint-disable unused-1, unused-2, used */", () => {
+ const parentComment = createParentComment([0, 45], " eslint-disable unused-1, unused-2, used ", ["unused-1", "unused-2", "used"]);
+
+ assert.deepStrictEqual(
+ applyDisableDirectives({
+ directives: [
+ {
+ parentComment,
+ ruleId: "unused-1",
+ type: "disable",
+ line: 1,
+ column: 18
+ },
+ {
+ parentComment,
+ ruleId: "unused-2",
+ type: "disable",
+ line: 1,
+ column: 28
+ },
+ {
+ parentComment,
+ ruleId: "used",
+ type: "disable",
+ line: 1,
+ column: 38
+ }
+ ],
+ 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
+ }
+ ]
+ );
+ });
+
+ it("Adds a problem for /* eslint-disable unused-1, unused-2, used, unused-3 */", () => {
+ const parentComment = createParentComment([0, 55], " eslint-disable unused-1, unused-2, used, unused-3 ", ["unused-1", "unused-2", "used", "unused-3"]);
+
+ assert.deepStrictEqual(
+ applyDisableDirectives({
+ directives: [
+ {
+ parentComment,
+ ruleId: "unused-1",
+ type: "disable",
+ line: 1,
+ column: 18
+ },
+ {
+ parentComment,
+ ruleId: "unused-2",
+ type: "disable",
+ line: 1,
+ column: 28
+ },
+ {
+ parentComment,
+ ruleId: "used",
+ type: "disable",
+ line: 1,
+ column: 38
+ },
+ {
+ parentComment,
+ ruleId: "unused-3",
+ type: "disable",
+ line: 1,
+ column: 43
+ }
+ ],
+ 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
+ }
+ ]
+ );
+ });
+
+ it("Adds a problem for /* eslint-disable unused-1, unused-2 */", () => {
+ const parentComment = createParentComment([0, 39], " eslint-disable unused-1, unused-2 ", ["unused-1", "unused-2"]);
+
+ assert.deepStrictEqual(
+ applyDisableDirectives({
+ directives: [
+ {
+ parentComment,
+ ruleId: "unused-1",
+ type: "disable",
+ line: 1,
+ column: 18
+ },
+ {
+ parentComment,
+ ruleId: "unused-2",
+ type: "disable",
+ line: 1,
+ column: 28
+ }
+ ],
+ problems: [],
+ reportUnusedDisableDirectives: "error"
+ }),
+ [
+ {
+ ruleId: null,
+ message: "Unused eslint-disable directive (no problems were reported from 'unused-1' or 'unused-2').",
+ line: 1,
+ column: 18,
+ fix: {
+ range: [0, 39],
+ text: " "
+ },
+ severity: 2,
+ nodeType: null
+ }
+ ]
+ );
+ });
+
+ it("Adds a problem for /* eslint-disable unused-1, unused-2, unused-3 */", () => {
+ const parentComment = createParentComment([0, 49], " eslint-disable unused-1, unused-2, unused-3 ", ["unused-1", "unused-2", "unused-3"]);
+
+ assert.deepStrictEqual(
+ applyDisableDirectives({
+ directives: [
+ {
+ parentComment,
+ ruleId: "unused-1",
+ type: "disable",
+ line: 1,
+ column: 18
+ },
+ {
+ parentComment,
+ ruleId: "unused-2",
+ type: "disable",
+ line: 1,
+ column: 28
+ },
+ {
+ parentComment,
+ ruleId: "unused-3",
+ type: "disable",
+ line: 1,
+ column: 38
+ }
+ ],
+ problems: [],
+ reportUnusedDisableDirectives: "error"
+ }),
+ [
+ {
+ ruleId: null,
+ message: "Unused eslint-disable directive (no problems were reported from 'unused-1', 'unused-2', or 'unused-3').",
+ line: 1,
+ column: 18,
+ fix: {
+ range: [0, 49],
+ text: " "
+ },
+ severity: 2,
+ nodeType: null
+ }
+ ]
+ );
+ });
+ });
});
}
}));
const messages = linter.verify(source, {
- parserOptions: { ecmaVersion: 2021 },
+ parserOptions: { ecmaVersion: 2022 },
rules: { test: 2 }
});
- assert.strictEqual(messages.length, 0);
+ 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) {
retv.push(codePath);
}
}));
- linter.verify(code, { rules: { test: 2 } });
+
+ linter.verify(code, {
+ rules: { test: 2 },
+ parserOptions: { ecmaVersion: "latest" }
+ });
return retv;
}
codePath.traverseSegments(options, (segment, controller) => {
retv.push(segment.id);
if (callback) {
- callback(segment, controller); // eslint-disable-line node/callback-return
+ callback(segment, controller); // eslint-disable-line node/callback-return -- At end of inner function
}
});
//------------------------------------------------------------------------------
describe("CodePathAnalyzer", () => {
+
+ /*
+ * If you need to output the code paths and DOT graph information for a
+ * particular piece of code, udpate 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 */
+ /* eslint-disable internal-rules/multiline-comment-style -- Commenting out */
it("simple", () => {
const codePath = parseCodePaths("foo(); bar(); baz();")[0];
const order = getOrderOfTraversing(codePath);
*/
});
- /* eslint-enable internal-rules/multiline-comment-style */
+ /* eslint-enable internal-rules/multiline-comment-style -- Commenting out */
});
});
"use strict";
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
const assert = require("chai").assert;
const interpolate = require("../../../lib/linter/interpolate");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
describe("interpolate()", () => {
it("passes through text without {{ }}", () => {
const message = "This is a very important message!";
const assert = require("chai").assert,
sinon = require("sinon"),
+ espree = require("espree"),
esprima = require("esprima"),
testParsers = require("../../fixtures/parsers/linter-test-parsers");
describe("Linter", () => {
const filename = "filename.js";
- /** @type {InstanceType<import("../../../lib/linter/linter.js")["Linter"]>} */
+ /** @type {InstanceType<import("../../../lib/linter/linter.js").Linter>} */
let linter;
beforeEach(() => {
assert.throws(() => {
linter.verify(code, config, filename);
- }, `Intentional error.\nOccurred while linting ${filename}:1`);
+ }, `Intentional error.\nOccurred while linting ${filename}:1\nRule: "checker"`);
});
it("does not call rule listeners with a `this` value", () => {
"eslint-enable eqeqeq",
"eslint-env es6"
]) {
- // eslint-disable-next-line no-loop-func
+ // eslint-disable-next-line no-loop-func -- No closures
it(`should warn '/* ${directive} */' if 'noInlineConfig' was given.`, () => {
const messages = linter.verify(`/* ${directive} */`, { noInlineConfig: true });
"eslint-disable-line eqeqeq",
"eslint-disable-next-line eqeqeq"
]) {
- // eslint-disable-next-line no-loop-func
+ // eslint-disable-next-line no-loop-func -- No closures
it(`should warn '// ${directive}' if 'noInlineConfig' was given.`, () => {
const messages = linter.verify(`// ${directive}`, { noInlineConfig: true });
message: "Unused eslint-disable directive (no problems were reported).",
line: 1,
column: 1,
+ fix: {
+ range: [0, 20],
+ text: " "
+ },
severity: 2,
nodeType: null
}
message: "Unused eslint-disable directive (no problems were reported).",
line: 1,
column: 1,
+ fix: {
+ range: [0, 20],
+ text: " "
+ },
severity: 2,
nodeType: null
}
message: "Unused eslint-disable directive (no problems were reported).",
line: 1,
column: 1,
+ fix: {
+ range: [0, 20],
+ text: " "
+ },
severity: 1,
nodeType: null
}
message: "Unused eslint-disable directive (no problems were reported).",
line: 1,
column: 1,
+ fix: {
+ range: [0, 20],
+ text: " "
+ },
severity: 1,
nodeType: null
}
]
);
});
+
+ 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
+ });
+
+ 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
+ }
+ ]
+ );
+ });
+
+ describe("autofix", () => {
+ const alwaysReportsRule = {
+ create(context) {
+ return {
+ Program(node) {
+ context.report({ message: "bad code", loc: node.loc.end });
+ }
+ };
+ }
+ };
+
+ 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: " "
+ },
+
+ //-----------------------------------------------
+ // 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-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*/"
+ },
+
+ // 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-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-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-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-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*/"
+ },
+
+ // 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-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, */"
+ },
+
+ // 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-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: `
+ /* eslint-disable used-1,
+ used-2
+ */
+ `
+ },
+ {
+ code: `
+ /* eslint-disable
+ unused-1,
+ used-1,
+ unused-2,
+ used-2
+ */
+ `,
+ output: `
+ /* eslint-disable
+ used-1,
+ used-2
+ */
+ `
+ },
+ {
+ code: `
+ /* eslint-disable
+ used-1,
+ unused-1,
+ used-2,
+ unused-2
+ */
+ `,
+ output: `
+ /* eslint-disable
+ used-1,
+ used-2
+ */
+ `
+ },
+ {
+ code: `
+ /* eslint-disable
+ used-1,
+ unused-1,
+ used-2,
+ unused-2,
+ */
+ `,
+ output: `
+ /* eslint-disable
+ used-1,
+ used-2,
+ */
+ `
+ },
+ {
+ code: `
+ /* eslint-disable
+ ,unused-1
+ ,used-1
+ ,unused-2
+ ,used-2
+ */
+ `,
+ output: `
+ /* eslint-disable
+ ,used-1
+ ,used-2
+ */
+ `
+ },
+ {
+ code: `
+ /* eslint-disable
+ ,used-1
+ ,unused-1
+ ,used-2
+ ,unused-2
+ */
+ `,
+ output: `
+ /* eslint-disable
+ ,used-1
+ ,used-2
+ */
+ `
+ },
+ {
+ code: `
+ /* eslint-disable
+ used-1,
+ unused-1,
+ used-2,
+ unused-2
+
+ -- comment
+ */
+ `,
+ output: `
+ /* eslint-disable
+ used-1,
+ used-2
+
+ -- 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
+ );
+ });
+ }
+ });
});
describe("when evaluating code with comments to change config when allowInlineConfig is disabled", () => {
return {};
});
- linter.defineRule("checker", filenameChecker);
linter.defineRule("checker", filenameChecker);
linter.verify("foo;", { rules: { checker: "error" } }, { filename: "foo.js" });
assert(filenameChecker.calledOnce);
});
describe("ecmaVersion", () => {
+
+ it("should not support ES6 when no ecmaVersion provided", () => {
+ const messages = linter.verify("let x = 0;");
+
+ assert.strictEqual(messages.length, 1);
+ });
+
+ it("supports ECMAScript version 'latest'", () => {
+ const messages = linter.verify("let x = 5 ** 7;", {
+ parserOptions: { ecmaVersion: "latest" }
+ });
+
+ assert.strictEqual(messages.length, 0);
+ });
+
+ it("the 'latest' is equal to espree.lastEcmaVersion", () => {
+ let ecmaVersion = null;
+ const config = { rules: { "ecma-version": 2 }, parserOptions: { ecmaVersion: "latest" } };
+
+ linter.defineRule("ecma-version", context => ({
+ Program() {
+ ecmaVersion = context.parserOptions.ecmaVersion;
+ }
+ }));
+ linter.verify("", config);
+ assert.strictEqual(ecmaVersion, espree.latestEcmaVersion);
+ });
+
+ it("should pass normalized ecmaVersion to eslint-scope", () => {
+ let blockScope = null;
+
+ linter.defineRule("block-scope", context => ({
+ BlockStatement() {
+ blockScope = context.getScope();
+ }
+ }));
+ linter.defineParser("custom-parser", {
+ parse: (...args) => espree.parse(...args)
+ });
+
+ // Use standard parser
+ linter.verify("{}", {
+ rules: { "block-scope": 2 },
+ parserOptions: { ecmaVersion: "latest" }
+ });
+
+ assert.strictEqual(blockScope.type, "block");
+
+ linter.verify("{}", {
+ rules: { "block-scope": 2 },
+ parserOptions: {} // ecmaVersion defaults to 5
+ });
+ assert.strictEqual(blockScope.type, "global");
+
+ // Use custom parser
+ linter.verify("{}", {
+ rules: { "block-scope": 2 },
+ parser: "custom-parser",
+ parserOptions: { ecmaVersion: "latest" }
+ });
+
+ assert.strictEqual(blockScope.type, "block");
+
+ linter.verify("{}", {
+ rules: { "block-scope": 2 },
+ parser: "custom-parser",
+ parserOptions: {} // ecmaVersion defaults to 5
+ });
+ assert.strictEqual(blockScope.type, "global");
+ });
+
describe("it should properly parse let declaration when", () => {
it("the ECMAScript version number is 6", () => {
const messages = linter.verify("let x = 5;", {
describe("suggestions", () => {
it("provides suggestion information for tools to use", () => {
- linter.defineRule("rule-with-suggestions", 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, " ")
- }]
- });
- }
- }));
+ 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" } });
messages: {
suggestion1: "Insert space at the beginning",
suggestion2: "Insert space at the end"
- }
+ },
+ hasSuggestions: true
},
create: context => ({
Program(node) {
}
}]);
});
+
+ 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", () => {
it("should use postprocessed problem ranges when applying autofixes", () => {
const code = "foo bar baz";
- linter.defineRule("capitalize-identifiers", context => ({
- Identifier(node) {
- if (node.name !== node.name.toUpperCase()) {
- context.report({
- node,
- message: "Capitalize this identifier",
- fix: fixer => fixer.replaceText(node, node.name.toUpperCase())
- });
- }
+ 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,
});
it("stops fixing after 10 passes", () => {
- linter.defineRule("add-spaces", context => ({
- Program(node) {
- context.report({
- node,
- message: "Add a space before this node.",
- fix: fixer => fixer.insertTextBefore(node, " ")
- });
+
+ 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.throws(() => {
linter.verify("0", { rules: { "test-rule": "error" } });
- }, /Fixable rules should export a `meta\.fixable` property.\nOccurred while linting <input>:1$/u);
+ }, /Fixable rules must set the `meta\.fixable` property to "code" or "whitespace".\nOccurred while linting <input>:1\nRule: "test-rule"$/u);
});
- it("should not throw an error if fix is passed and there is no metadata", () => {
+ it("should throw an error if fix is passed and there is no metadata", () => {
linter.defineRule("test-rule", {
create: context => ({
Program(node) {
})
});
- linter.verify("0", { rules: { "test-rule": "error" } });
+ 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", 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("eslint-scope should use the visitorKeys (so 'childVisitorKeys.ClassDeclaration' includes 'experimentalDecorators')", () => {
assert.deepStrictEqual(
- scopeManager.__options.childVisitorKeys.ClassDeclaration, // eslint-disable-line no-underscore-dangle
+ scopeManager.__options.childVisitorKeys.ClassDeclaration, // eslint-disable-line no-underscore-dangle -- ScopeManager API
["experimentalDecorators", "id", "superClass", "body"]
);
});
assert.strictEqual(messages.length, 0);
});
});
+
+ describe("merging 'parserOptions'", () => {
+ it("should deeply merge 'parserOptions' from an environment with 'parserOptions' from the provided config", () => {
+ const code = "return <div/>";
+ const config = {
+ env: {
+ node: true // ecmaFeatures: { globalReturn: true }
+ },
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true
+ }
+ }
+ };
+
+ const messages = linter.verify(code, config);
+
+ // no parsing errors
+ assert.strictEqual(messages.length, 0);
+ });
+ });
});
createEmitter = require("../../../lib/linter/safe-emitter"),
NodeEventGenerator = require("../../../lib/linter/node-event-generator");
+
//------------------------------------------------------------------------------
-// Tests
+// Constants
//------------------------------------------------------------------------------
const ESPREE_CONFIG = {
const STANDARD_ESQUERY_OPTION = { visitorKeys: vk.KEYS, fallback: Traverser.getKeys };
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
describe("NodeEventGenerator", () => {
EventGeneratorTester.testEventGeneratorInterface(
new NodeEventGenerator(createEmitter(), STANDARD_ESQUERY_OPTION)
]
);
+ 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;",
[
["[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", () => {
);
});
+ 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,
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
+ // eslint-disable-next-line no-loop-func -- Using arrow functions
() => translateReport(
{ node, messageId: "testMessage", fix: () => ({ range: badRange, text: "foo" }) }
),
);
assert.throws(
- // eslint-disable-next-line no-loop-func
+ // eslint-disable-next-line no-loop-func -- Using arrow functions
() => translateReport(
{
node,
});
+ it("should allow inserting empty text", () => {
+
+ const result = ruleFixer.insertTextBefore({ range: [10, 20] }, "");
+
+ assert.deepStrictEqual(result, {
+ range: [10, 10],
+ text: ""
+ });
+
+ });
+
});
describe("insertTextBeforeRange", () => {
});
+ it("should allow inserting empty text", () => {
+
+ const result = ruleFixer.insertTextBeforeRange([10, 20], "");
+
+ assert.deepStrictEqual(result, {
+ range: [10, 10],
+ text: ""
+ });
+
+ });
+
});
describe("insertTextAfter", () => {
});
+ it("should allow inserting empty text", () => {
+
+ const result = ruleFixer.insertTextAfter({ range: [10, 20] }, "");
+
+ assert.deepStrictEqual(result, {
+ range: [20, 20],
+ text: ""
+ });
+
+ });
+
});
describe("insertTextAfterRange", () => {
});
+ it("should allow inserting empty text", () => {
+
+ const result = ruleFixer.insertTextAfterRange([10, 20], "");
+
+ assert.deepStrictEqual(result, {
+ range: [20, 20],
+ text: ""
+ });
+
+ });
+
});
describe("removeAfter", () => {
"use strict";
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
const createEmitter = require("../../../lib/linter/safe-emitter");
const assert = require("chai").assert;
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
describe("safe-emitter", () => {
describe("emit() and on()", () => {
it("allows listeners to be registered calls them when emitted", () => {
let called = false;
emitter.on("foo", function() {
- assert.strictEqual(this, void 0); // eslint-disable-line no-invalid-this
+ assert.strictEqual(this, void 0); // eslint-disable-line no-invalid-this -- Checking `this` value
called = true;
});
"use strict";
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
const { getListSize } = require("../../../lib/linter/timing");
const assert = require("chai").assert;
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
describe("timing", () => {
describe("getListSize()", () => {
after(() => {
+/* eslint no-global-assign: off -- Resetting Mocha globals */
/**
* @fileoverview Tests for RuleTester without any test runner
* @author Weijia Wang <starkwang@126.com>
*/
"use strict";
-/* eslint-disable no-global-assign*/
const assert = require("assert");
const { RuleTester } = require("../../../lib/rule-tester");
const tmpIt = it;
EventEmitter = require("events"),
{ RuleTester } = require("../../../lib/rule-tester"),
assert = require("chai").assert,
- nodeAssert = require("assert");
+ nodeAssert = require("assert"),
+ espree = require("espree");
const NODE_ASSERT_STRICT_EQUAL_OPERATOR = (() => {
try {
ruleTester = new RuleTester();
});
+ describe("only", () => {
+ describe("`itOnly` accessor", () => {
+ describe("when `itOnly` is set", () => {
+ before(() => {
+ RuleTester.itOnly = sinon.spy();
+ });
+ after(() => {
+ RuleTester.itOnly = void 0;
+ });
+ beforeEach(() => {
+ RuleTester.itOnly.resetHistory();
+ ruleTester = new RuleTester();
+ });
+
+ it("is called by exclusive tests", () => {
+ ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
+ valid: [{
+ code: "const notVar = 42;",
+ only: true
+ }],
+ invalid: []
+ });
+
+ sinon.assert.calledWith(RuleTester.itOnly, "const notVar = 42;");
+ });
+ });
+
+ describe("when `it` is set and has an `only()` method", () => {
+ before(() => {
+ RuleTester.it.only = () => {};
+ sinon.spy(RuleTester.it, "only");
+ });
+ after(() => {
+ RuleTester.it.only = void 0;
+ });
+ beforeEach(() => {
+ RuleTester.it.only.resetHistory();
+ ruleTester = new RuleTester();
+ });
+
+ it("is called by tests with `only` set", () => {
+ ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
+ valid: [{
+ code: "const notVar = 42;",
+ only: true
+ }],
+ invalid: []
+ });
+
+ sinon.assert.calledWith(RuleTester.it.only, "const notVar = 42;");
+ });
+ });
+
+ describe("when global `it` is a function that has an `only()` method", () => {
+ let originalGlobalItOnly;
+
+ before(() => {
+
+ /*
+ * We run tests with `--forbid-only`, so we have to override
+ * `it.only` to prevent the real one from being called.
+ */
+ originalGlobalItOnly = it.only;
+ it.only = () => {};
+ sinon.spy(it, "only");
+ });
+ after(() => {
+ it.only = originalGlobalItOnly;
+ });
+ beforeEach(() => {
+ it.only.resetHistory();
+ ruleTester = new RuleTester();
+ });
+
+ it("is called by tests with `only` set", () => {
+ ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
+ valid: [{
+ code: "const notVar = 42;",
+ only: true
+ }],
+ invalid: []
+ });
+
+ sinon.assert.calledWith(it.only, "const notVar = 42;");
+ });
+ });
+
+ describe("when `describe` and `it` are overridden without `itOnly`", () => {
+ let originalGlobalItOnly;
+
+ before(() => {
+
+ /*
+ * These tests override `describe` and `it` already, so we
+ * don't need to override them here. We do, however, need to
+ * remove `only` from the global `it` to prevent it from
+ * being used instead.
+ */
+ originalGlobalItOnly = it.only;
+ it.only = void 0;
+ });
+ after(() => {
+ it.only = originalGlobalItOnly;
+ });
+ beforeEach(() => {
+ ruleTester = new RuleTester();
+ });
+
+ it("throws an error recommending overriding `itOnly`", () => {
+ assert.throws(() => {
+ ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
+ valid: [{
+ code: "const notVar = 42;",
+ only: true
+ }],
+ invalid: []
+ });
+ }, "Set `RuleTester.itOnly` to use `only` with a custom test framework.");
+ });
+ });
+
+ describe("when global `it` is a function that does not have an `only()` method", () => {
+ let originalGlobalIt;
+ let originalRuleTesterDescribe;
+ let originalRuleTesterIt;
+
+ before(() => {
+ originalGlobalIt = global.it;
+
+ // eslint-disable-next-line no-global-assign -- Temporarily override Mocha global
+ it = () => {};
+
+ /*
+ * These tests override `describe` and `it`, so we need to
+ * un-override them here so they won't interfere.
+ */
+ originalRuleTesterDescribe = RuleTester.describe;
+ RuleTester.describe = void 0;
+ originalRuleTesterIt = RuleTester.it;
+ RuleTester.it = void 0;
+ });
+ after(() => {
+
+ // eslint-disable-next-line no-global-assign -- Restore Mocha global
+ it = originalGlobalIt;
+ RuleTester.describe = originalRuleTesterDescribe;
+ RuleTester.it = originalRuleTesterIt;
+ });
+ beforeEach(() => {
+ ruleTester = new RuleTester();
+ });
+
+ it("throws an error explaining that the current test framework does not support `only`", () => {
+ assert.throws(() => {
+ ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
+ valid: [{
+ code: "const notVar = 42;",
+ only: true
+ }],
+ invalid: []
+ });
+ }, "The current test framework does not support exclusive tests with `only`.");
+ });
+ });
+ });
+
+ describe("test cases", () => {
+ const ruleName = "no-var";
+ const rule = require("../../fixtures/testers/rule-tester/no-var");
+
+ let originalRuleTesterIt;
+ let spyRuleTesterIt;
+ let originalRuleTesterItOnly;
+ let spyRuleTesterItOnly;
+
+ before(() => {
+ originalRuleTesterIt = RuleTester.it;
+ spyRuleTesterIt = sinon.spy();
+ RuleTester.it = spyRuleTesterIt;
+ originalRuleTesterItOnly = RuleTester.itOnly;
+ spyRuleTesterItOnly = sinon.spy();
+ RuleTester.itOnly = spyRuleTesterItOnly;
+ });
+ after(() => {
+ RuleTester.it = originalRuleTesterIt;
+ RuleTester.itOnly = originalRuleTesterItOnly;
+ });
+ beforeEach(() => {
+ spyRuleTesterIt.resetHistory();
+ spyRuleTesterItOnly.resetHistory();
+ ruleTester = new RuleTester();
+ });
+
+ it("isn't called for normal tests", () => {
+ ruleTester.run(ruleName, rule, {
+ valid: ["const notVar = 42;"],
+ invalid: []
+ });
+ sinon.assert.calledWith(spyRuleTesterIt, "const notVar = 42;");
+ sinon.assert.notCalled(spyRuleTesterItOnly);
+ });
+
+ it("calls it or itOnly for every test case", () => {
+
+ /*
+ * `RuleTester` doesn't implement test case exclusivity itself.
+ * Setting `only: true` just causes `RuleTester` to call
+ * whatever `only()` function is provided by the test framework
+ * instead of the regular `it()` function.
+ */
+
+ ruleTester.run(ruleName, rule, {
+ valid: [
+ "const valid = 42;",
+ {
+ code: "const onlyValid = 42;",
+ only: true
+ }
+ ],
+ invalid: [
+ {
+ code: "var invalid = 42;",
+ errors: [/^Bad var/u]
+ },
+ {
+ code: "var onlyInvalid = 42;",
+ errors: [/^Bad var/u],
+ only: true
+ }
+ ]
+ });
+
+ sinon.assert.calledWith(spyRuleTesterIt, "const valid = 42;");
+ sinon.assert.calledWith(spyRuleTesterItOnly, "const onlyValid = 42;");
+ sinon.assert.calledWith(spyRuleTesterIt, "var invalid = 42;");
+ sinon.assert.calledWith(spyRuleTesterItOnly, "var onlyInvalid = 42;");
+ });
+ });
+
+ describe("static helper wrapper", () => {
+ it("adds `only` to string test cases", () => {
+ const test = RuleTester.only("const valid = 42;");
+
+ assert.deepStrictEqual(test, {
+ code: "const valid = 42;",
+ only: true
+ });
+ });
+
+ it("adds `only` to object test cases", () => {
+ const test = RuleTester.only({ code: "const valid = 42;" });
+
+ assert.deepStrictEqual(test, {
+ code: "const valid = 42;",
+ only: true
+ });
+ });
+ });
+ });
+
it("should not throw an error when everything passes", () => {
ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
valid: [
});
assert.strictEqual(spy.args[1][1].parser, require.resolve("esprima"));
});
+ it("should pass normalized ecmaVersion to the rule", () => {
+ const reportEcmaVersionRule = {
+ meta: {
+ messages: {
+ ecmaVersionMessage: "context.parserOptions.ecmaVersion is {{type}} {{ecmaVersion}}."
+ }
+ },
+ create: context => ({
+ Program(node) {
+ const { ecmaVersion } = context.parserOptions;
+
+ context.report({
+ node,
+ messageId: "ecmaVersionMessage",
+ data: { type: typeof ecmaVersion, ecmaVersion }
+ });
+ }
+ })
+ };
+
+ const notEspree = require.resolve("../../fixtures/parsers/empty-program-parser");
+
+ ruleTester.run("report-ecma-version", reportEcmaVersionRule, {
+ valid: [],
+ invalid: [
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }]
+ },
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }],
+ parserOptions: {}
+ },
+ {
+ code: "<div/>",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }],
+ parserOptions: { ecmaFeatures: { jsx: true } }
+ },
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }],
+ parser: require.resolve("espree")
+ },
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
+ parserOptions: { ecmaVersion: 6 }
+ },
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
+ parserOptions: { ecmaVersion: 2015 }
+ },
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }],
+ env: { browser: true }
+ },
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }],
+ env: { es6: false }
+ },
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
+ env: { es6: true }
+ },
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "8" } }],
+ env: { es6: false, es2017: true }
+ },
+ {
+ code: "let x",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
+ env: { es6: "truthy" }
+ },
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "8" } }],
+ env: { es2017: true }
+ },
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "11" } }],
+ env: { es2020: true }
+ },
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "12" } }],
+ env: { es2021: true }
+ },
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }],
+ parserOptions: { ecmaVersion: "latest" }
+ },
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }],
+ parser: require.resolve("espree"),
+ parserOptions: { ecmaVersion: "latest" }
+ },
+ {
+ code: "<div/>",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }],
+ parserOptions: { ecmaVersion: "latest", ecmaFeatures: { jsx: true } }
+ },
+ {
+ code: "import 'foo'",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }],
+ parserOptions: { ecmaVersion: "latest", sourceType: "module" }
+ },
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }],
+ parserOptions: { ecmaVersion: "latest" },
+ env: { es6: true }
+ },
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }],
+ parserOptions: { ecmaVersion: "latest" },
+ env: { es2020: true }
+ },
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }],
+ parser: notEspree
+ },
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }],
+ parser: notEspree,
+ parserOptions: {}
+ },
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "5" } }],
+ parser: notEspree,
+ parserOptions: { ecmaVersion: 5 }
+ },
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
+ parser: notEspree,
+ parserOptions: { ecmaVersion: 6 }
+ },
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
+ parser: notEspree,
+ parserOptions: { ecmaVersion: 2015 }
+ },
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "string", ecmaVersion: "latest" } }],
+ parser: notEspree,
+ parserOptions: { ecmaVersion: "latest" }
+ }
+ ]
+ });
+
+ [{ parserOptions: { ecmaVersion: 6 } }, { env: { es6: true } }].forEach(options => {
+ new RuleTester(options).run("report-ecma-version", reportEcmaVersionRule, {
+ valid: [],
+ invalid: [
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }]
+ },
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }],
+ parserOptions: {}
+ }
+ ]
+ });
+ });
+
+ new RuleTester({ parser: notEspree }).run("report-ecma-version", reportEcmaVersionRule, {
+ valid: [],
+ invalid: [
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }]
+ },
+ {
+ code: "",
+ errors: [{ messageId: "ecmaVersionMessage", data: { type: "string", ecmaVersion: "latest" } }],
+ parserOptions: { ecmaVersion: "latest" }
+ }
+ ]
+ });
+ });
it("should pass-through services from parseForESLint to the rule", () => {
const enhancedParserPath = require.resolve("../../fixtures/parsers/enhanced-parser");
{ code: "var foo = bar;", output: "5", errors: 1 }
]
});
- }, "Fixable rules should export a `meta.fixable` property.");
+ }, /Fixable rules must set the `meta\.fixable` property/u);
});
it("should throw an error if a legacy-format rule produces fixes", () => {
{ code: "var foo = bar;", output: "5", errors: 1 }
]
});
- }, "Fixable rules should export a `meta.fixable` property.");
+ }, /Fixable rules must set the `meta\.fixable` property/u);
});
describe("suggestions", () => {
});
}, /Invalid suggestion property name 'outpt'/u);
});
+
+ it("should throw an error if a rule that doesn't have `meta.hasSuggestions` enabled produces suggestions", () => {
+ assert.throws(() => {
+ ruleTester.run("suggestions-missing-hasSuggestions-property", require("../../fixtures/testers/rule-tester/suggestions").withoutHasSuggestionsProperty, {
+ valid: [],
+ invalid: [
+ { code: "var foo = bar;", output: "5", errors: 1 }
+ ]
+ });
+ }, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`.");
+ });
});
describe("naming test cases", () => {
* 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 {*} 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.
+ * @param {any} expectedValue The value that should be emitted
+ * @returns {Promise<void>} 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 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;
+ });
});
// https://github.com/eslint/eslint/issues/11615
assert.throw(() => {
ruleTester.run(
"foo",
- context => ({
- Identifier(node) {
- context.report({
- node,
- message: "make a syntax error",
- fix(fixer) {
- return fixer.replaceText(node, "one two");
+ {
+ 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: []
});
+ describe("SourceCode#getComments()", () => {
+ const useGetCommentsRule = {
+ create: context => ({
+ Program(node) {
+ const sourceCode = context.getSourceCode();
+
+ sourceCode.getComments(node);
+ }
+ })
+ };
+
+ it("should throw if called from a valid test case", () => {
+ assert.throws(() => {
+ ruleTester.run("use-get-comments", useGetCommentsRule, {
+ valid: [""],
+ invalid: []
+ });
+ }, /`SourceCode#getComments\(\)` is deprecated/u);
+ });
+
+ it("should throw if called from an invalid test case", () => {
+ assert.throws(() => {
+ ruleTester.run("use-get-comments", useGetCommentsRule, {
+ valid: [],
+ invalid: [{
+ code: "",
+ errors: [{}]
+ }]
+ });
+ }, /`SourceCode#getComments\(\)` is deprecated/u);
+ });
+ });
+
});
options: [{ enforceForClassMembers: true }],
parserOptions: { ecmaVersion: 6 }
},
+ {
+ code: "class A { get #a() {} }",
+ options: [{ enforceForClassMembers: true }],
+ parserOptions: { ecmaVersion: 13 }
+ },
// Explicitly disabled option
{
parserOptions: { ecmaVersion: 6 },
errors: [{ message: "Getter is not present for class static setter 'a'.", type: "MethodDefinition" }]
},
+ {
+ code: "class A { set '#a'(foo) {} }",
+ parserOptions: { ecmaVersion: 13 },
+ errors: [{ message: "Getter is not present for class setter '#a'.", type: "MethodDefinition" }]
+ },
+ {
+ code: "class A { set #a(foo) {} }",
+ parserOptions: { ecmaVersion: 13 },
+ errors: [{ message: "Getter is not present for class private setter #a.", type: "MethodDefinition" }]
+ },
+ {
+ code: "class A { static set '#a'(foo) {} }",
+ parserOptions: { ecmaVersion: 13 },
+ errors: [{ message: "Getter is not present for class static setter '#a'.", type: "MethodDefinition" }]
+ },
+ {
+ code: "class A { static set #a(foo) {} }",
+ parserOptions: { ecmaVersion: 13 },
+ errors: [{ message: "Getter is not present for class static private setter #a.", type: "MethodDefinition" }]
+ },
// Test that the accessor kind options do not affect each other
{
parserOptions: { 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 }],
+ parserOptions: { 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 }],
+ parserOptions: { 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 }],
+ parserOptions: { 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 }],
+ parserOptions: { ecmaVersion: 13 },
+ errors: [{ message: "Setter is not present for class static private getter #a.", type: "MethodDefinition" }]
+ },
// Various kinds of keys
{
{ 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 }],
+ parserOptions: { 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 }],
+ parserOptions: { 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 }
+ ]
+ },
// Prototype and static accessors with same keys
{
const rule = require("../../../lib/rules/array-callback-return"),
{ RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester();
const allowImplicitOptions = [{ allowImplicit: true }];
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
const valid = [
// https://github.com/eslint/eslint/issues/2967
"(function () { foo(); })(); function foo() {}",
- { code: "(function () { foo(); })(); function foo() {}", parserOptions: { ecmaVersion: 6, sourceType: "module" } }
+ { code: "(function () { foo(); })(); function foo() {}", parserOptions: { ecmaVersion: 6, sourceType: "module" } },
+
+ { code: "class C { static { var foo; foo; } }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { foo; var foo; } }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { if (bar) { foo; } var foo; } }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "var foo; class C { static { foo; } } ", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { foo; } } var foo;", parserOptions: { ecmaVersion: 2022 } },
+ { code: "var foo; class C { static {} [foo]; } ", parserOptions: { ecmaVersion: 2022 } },
+ { code: "foo; class C { static {} } var foo; ", parserOptions: { ecmaVersion: 2022 } }
],
invalid: [
{ code: "function f(){ x; { var x; } }", errors: [{ messageId: "outOfScope", data: { name: "x" }, type: "Identifier" }] },
{ messageId: "outOfScope", data: { name: "i" }, type: "Identifier" },
{ messageId: "outOfScope", data: { name: "i" }, type: "Identifier" }
]
+ },
+ {
+ code: "class C { static { if (bar) { var foo; } foo; } }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "outOfScope", data: { name: "foo" }, type: "Identifier" }]
}
]
});
{ code: "(() => { bar(); });", parserOptions: { ecmaVersion: 6 } },
"if (a) { /* comment */ foo(); /* comment */ }",
"if (a) { //comment\n foo(); }",
+ { code: "class C { static {} }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { foo; } }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { /* comment */foo;/* comment */ } }", parserOptions: { ecmaVersion: 2022 } },
// never
{ code: "{foo();}", options: ["never"] },
{ code: "(function() {bar();});", options: ["never"] },
{ code: "(() => {bar();});", options: ["never"], parserOptions: { ecmaVersion: 6 } },
{ code: "if (a) {/* comment */ foo(); /* comment */}", options: ["never"] },
- { code: "if (a) { //comment\n foo();}", options: ["never"] }
+ { code: "if (a) { //comment\n foo();}", options: ["never"] },
+ { code: "class C { static { } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static {foo;} }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static {/* comment */ foo; /* comment */} }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { // line comment is allowed\n foo;\n} }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static {\nfoo;\n} }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { \n foo; \n } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }
],
invalid: [
]
},
+ // class static blocks
+ {
+ code: "class C { static {foo; } }",
+ output: "class C { static { foo; } }",
+ parserOptions: { 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; } }",
+ parserOptions: { 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; } }",
+ parserOptions: { 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 */ } }",
+ parserOptions: { 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 */ } }",
+ parserOptions: { 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() } }",
+ parserOptions: { 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
{
endColumn: 21
}
]
+ },
+
+ // class static blocks
+ {
+ code: "class C { static { foo;} }",
+ output: "class C { static {foo;} }",
+ options: ["never"],
+ parserOptions: { 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"],
+ parserOptions: { 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"],
+ parserOptions: { 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"],
+ parserOptions: { 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"],
+ parserOptions: { 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"],
+ parserOptions: { 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
+ }
+ ]
}
]
});
//------------------------------------------------------------------------------
const rule = require("../../../lib/rules/brace-style"),
- { RuleTester } = require("../../../lib/rule-tester");
+ { RuleTester } = require("../../../lib/rule-tester"),
+ { unIndent } = require("../../_utils");
//------------------------------------------------------------------------------
// Tests
`
if (foo) bar = function() {}
else baz()
- `
+ `,
+
+ // class static blocks
+ {
+ code: unIndent`
+ class C {
+ static {
+ foo;
+ }
+ }
+ `,
+ options: ["1tbs"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C {
+ static {}
+
+ static {
+ }
+ }
+ `,
+ options: ["1tbs"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C {
+ static { foo; }
+ }
+ `,
+ options: ["1tbs", { allowSingleLine: true }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ foo;
+ }
+ }
+ `,
+ options: ["stroustrup"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C {
+ static {}
+
+ static {
+ }
+ }
+ `,
+ options: ["stroustrup"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C {
+ static { foo; }
+ }
+ `,
+ options: ["stroustrup", { allowSingleLine: true }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C
+ {
+ static
+ {
+ foo;
+ }
+ }
+ `,
+ options: ["allman"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C
+ {
+ static
+ {}
+ }
+ `,
+ options: ["allman"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C
+ {
+ static {}
+
+ static { foo; }
+
+ static
+ { foo; }
+ }
+ `,
+ options: ["allman", { allowSingleLine: true }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ {
+ foo;
+ }
+ }
+ }
+ `,
+ options: ["1tbs"],
+ parserOptions: { ecmaVersion: 2022 }
+ }
],
invalid: [
{ 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 C {
+ static
+ {
+ foo;
+ }
+ }
+ `,
+ output: unIndent`
+ class C {
+ static {
+ foo;
+ }
+ }
+ `,
+ options: ["1tbs"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "nextLineOpen", type: "Punctuator" }
+ ]
+ },
+ {
+ code: unIndent`
+ class C {
+ static {foo;
+ }
+ }
+ `,
+ output: unIndent`
+ class C {
+ static {
+ foo;
+ }
+ }
+ `,
+ options: ["1tbs"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "blockSameLine", type: "Punctuator" }
+ ]
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ foo;}
+ }
+ `,
+ output: unIndent`
+ class C {
+ static {
+ foo;
+ }
+ }
+ `,
+ options: ["1tbs"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "singleLineClose", type: "Punctuator" }
+ ]
+ },
+ {
+ code: unIndent`
+ class C {
+ static
+ {foo;}
+ }
+ `,
+ output: unIndent`
+ class C {
+ static {
+ foo;
+ }
+ }
+ `,
+ options: ["1tbs"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "nextLineOpen", type: "Punctuator" },
+ { messageId: "blockSameLine", type: "Punctuator" },
+ { messageId: "singleLineClose", type: "Punctuator" }
+ ]
+ },
+ {
+ code: unIndent`
+ class C {
+ static
+ {}
+ }
+ `,
+ output: unIndent`
+ class C {
+ static {}
+ }
+ `,
+ options: ["1tbs"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "nextLineOpen", type: "Punctuator" }
+ ]
+ },
+ {
+ code: unIndent`
+ class C {
+ static
+ {
+ foo;
+ }
+ }
+ `,
+ output: unIndent`
+ class C {
+ static {
+ foo;
+ }
+ }
+ `,
+ options: ["stroustrup"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "nextLineOpen", type: "Punctuator" }
+ ]
+ },
+ {
+ code: unIndent`
+ class C {
+ static {foo;
+ }
+ }
+ `,
+ output: unIndent`
+ class C {
+ static {
+ foo;
+ }
+ }
+ `,
+ options: ["stroustrup"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "blockSameLine", type: "Punctuator" }
+ ]
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ foo;}
+ }
+ `,
+ output: unIndent`
+ class C {
+ static {
+ foo;
+ }
+ }
+ `,
+ options: ["stroustrup"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "singleLineClose", type: "Punctuator" }
+ ]
+ },
+ {
+ code: unIndent`
+ class C {
+ static
+ {foo;}
+ }
+ `,
+ output: unIndent`
+ class C {
+ static {
+ foo;
+ }
+ }
+ `,
+ options: ["stroustrup"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "nextLineOpen", type: "Punctuator" },
+ { messageId: "blockSameLine", type: "Punctuator" },
+ { messageId: "singleLineClose", type: "Punctuator" }
+ ]
+ },
+ {
+ code: unIndent`
+ class C {
+ static
+ {}
+ }
+ `,
+ output: unIndent`
+ class C {
+ static {}
+ }
+ `,
+ options: ["stroustrup"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "nextLineOpen", type: "Punctuator" }
+ ]
+ },
+ {
+ code: unIndent`
+ class C
+ {
+ static{
+ foo;
+ }
+ }
+ `,
+ output: unIndent`
+ class C
+ {
+ static
+ {
+ foo;
+ }
+ }
+ `,
+ options: ["allman"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "sameLineOpen", type: "Punctuator" }
+ ]
+ },
+ {
+ code: unIndent`
+ class C
+ {
+ static
+ {foo;
+ }
+ }
+ `,
+ output: unIndent`
+ class C
+ {
+ static
+ {
+ foo;
+ }
+ }
+ `,
+ options: ["allman"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "blockSameLine", type: "Punctuator" }
+ ]
+ },
+ {
+ code: unIndent`
+ class C
+ {
+ static
+ {
+ foo;}
+ }
+ `,
+ output: unIndent`
+ class C
+ {
+ static
+ {
+ foo;
+ }
+ }
+ `,
+ options: ["allman"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "singleLineClose", type: "Punctuator" }
+ ]
+ },
+ {
+ code: unIndent`
+ class C
+ {
+ static{foo;}
+ }
+ `,
+ output: unIndent`
+ class C
+ {
+ static
+ {
+ foo;
+ }
+ }
+ `,
+ options: ["allman"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "sameLineOpen", type: "Punctuator" },
+ { messageId: "blockSameLine", type: "Punctuator" },
+ { messageId: "singleLineClose", type: "Punctuator" }
+ ]
+ },
+ {
+ code: unIndent`
+ class C
+ {
+ static{}
+ }
+ `,
+ output: unIndent`
+ class C
+ {
+ static
+ {}
+ }
+ `,
+ options: ["allman"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "sameLineOpen", type: "Punctuator" }
+ ]
}
]
});
{
code: "var camelCased = a_global_variable",
options: [{ ignoreGlobals: true }],
- globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+ globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase -- Testing non-CamelCase
},
{
code: "a_global_variable.foo()",
options: [{ ignoreGlobals: true }],
- globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+ globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase -- Testing non-CamelCase
},
{
code: "a_global_variable[undefined]",
options: [{ ignoreGlobals: true }],
- globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+ globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase -- Testing non-CamelCase
},
{
code: "var foo = a_global_variable.bar",
options: [{ ignoreGlobals: true }],
- globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+ globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase -- Testing non-CamelCase
},
{
code: "a_global_variable.foo = bar",
options: [{ ignoreGlobals: true }],
- globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+ globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase -- Testing non-CamelCase
},
{
code: "( { foo: a_global_variable.bar } = baz )",
options: [{ ignoreGlobals: true }],
parserOptions: { ecmaVersion: 6 },
- globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+ globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase -- Testing non-CamelCase
},
{
code: "a_global_variable = foo",
options: [{ ignoreGlobals: true }],
- globals: { a_global_variable: "writable" } // eslint-disable-line camelcase
+ globals: { a_global_variable: "writable" } // eslint-disable-line camelcase -- Testing non-CamelCase
},
{
code: "a_global_variable = foo",
options: [{ ignoreGlobals: true }],
- globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+ globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase -- Testing non-CamelCase
},
{
code: "({ a_global_variable } = foo)",
options: [{ ignoreGlobals: true }],
parserOptions: { ecmaVersion: 6 },
- globals: { a_global_variable: "writable" } // eslint-disable-line camelcase
+ globals: { a_global_variable: "writable" } // eslint-disable-line camelcase -- Testing non-CamelCase
},
{
code: "({ snake_cased: a_global_variable } = foo)",
options: [{ ignoreGlobals: true }],
parserOptions: { ecmaVersion: 6 },
- globals: { a_global_variable: "writable" } // eslint-disable-line camelcase
+ globals: { a_global_variable: "writable" } // eslint-disable-line camelcase -- Testing non-CamelCase
},
{
code: "({ snake_cased: a_global_variable = foo } = bar)",
options: [{ ignoreGlobals: true }],
parserOptions: { ecmaVersion: 6 },
- globals: { a_global_variable: "writable" } // eslint-disable-line camelcase
+ globals: { a_global_variable: "writable" } // eslint-disable-line camelcase -- Testing non-CamelCase
},
{
code: "[a_global_variable] = bar",
options: [{ ignoreGlobals: true }],
parserOptions: { ecmaVersion: 6 },
- globals: { a_global_variable: "writable" } // eslint-disable-line camelcase
+ globals: { a_global_variable: "writable" } // eslint-disable-line camelcase -- Testing non-CamelCase
},
{
code: "[a_global_variable = foo] = bar",
options: [{ ignoreGlobals: true }],
parserOptions: { ecmaVersion: 6 },
- globals: { a_global_variable: "writable" } // eslint-disable-line camelcase
+ globals: { a_global_variable: "writable" } // eslint-disable-line camelcase -- Testing non-CamelCase
},
{
code: "foo[a_global_variable] = bar",
options: [{ ignoreGlobals: true }],
- globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+ globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase -- Testing non-CamelCase
},
{
code: "var foo = { [a_global_variable]: bar }",
options: [{ ignoreGlobals: true }],
parserOptions: { ecmaVersion: 6 },
- globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+ globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase -- Testing non-CamelCase
},
{
code: "var { [a_global_variable]: foo } = bar",
options: [{ ignoreGlobals: true }],
parserOptions: { ecmaVersion: 6 },
- globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+ globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase -- Testing non-CamelCase
},
{
code: "function foo({ no_camelcased: camelCased }) {};",
code: "([obj.foo = obj.fo_o] = bar);",
options: [{ properties: "always" }],
parserOptions: { ecmaVersion: 6 }
+ },
+ {
+ code: "class C { camelCase; #camelCase; #camelCase2() {} }",
+ options: [{ properties: "always" }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { snake_case; #snake_case; #snake_case2() {} }",
+ options: [{ properties: "never" }],
+ parserOptions: { ecmaVersion: 2022 }
}
],
invalid: [
{
code: "var camelCased = snake_cased",
options: [{ ignoreGlobals: false }],
- globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase
+ globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase -- Testing non-CamelCase
errors: [
{
messageId: "notCamelCase",
{
code: "a_global_variable.foo()",
options: [{ ignoreGlobals: false }],
- globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase
+ globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase -- Testing non-CamelCase
errors: [
{
messageId: "notCamelCase",
{
code: "a_global_variable[undefined]",
options: [{ ignoreGlobals: false }],
- globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase
+ globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase -- Testing non-CamelCase
errors: [
{
messageId: "notCamelCase",
},
{
code: "var camelCased = snake_cased",
- globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase
+ globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase -- Testing non-CamelCase
errors: [
{
messageId: "notCamelCase",
{
code: "var camelCased = snake_cased",
options: [{}],
- globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase
+ globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase -- Testing non-CamelCase
errors: [
{
messageId: "notCamelCase",
{
code: "foo.a_global_variable = bar",
options: [{ ignoreGlobals: true }],
- globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+ globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase -- Testing non-CamelCase
errors: [
{
messageId: "notCamelCase",
{
code: "var foo = { a_global_variable: bar }",
options: [{ ignoreGlobals: true }],
- globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+ globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase -- Testing non-CamelCase
errors: [
{
messageId: "notCamelCase",
{
code: "var foo = { a_global_variable: a_global_variable }",
options: [{ ignoreGlobals: true }],
- globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+ globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase -- Testing non-CamelCase
errors: [
{
messageId: "notCamelCase",
code: "var foo = { a_global_variable() {} }",
options: [{ ignoreGlobals: true }],
parserOptions: { ecmaVersion: 6 },
- globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+ globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase -- Testing non-CamelCase
errors: [
{
messageId: "notCamelCase",
code: "class Foo { a_global_variable() {} }",
options: [{ ignoreGlobals: true }],
parserOptions: { ecmaVersion: 6 },
- globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+ globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase -- Testing non-CamelCase
errors: [
{
messageId: "notCamelCase",
{
code: "a_global_variable: for (;;);",
options: [{ ignoreGlobals: true }],
- globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+ globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase -- Testing non-CamelCase
errors: [
{
messageId: "notCamelCase",
code: "if (foo) { let a_global_variable; a_global_variable = bar; }",
options: [{ ignoreGlobals: true }],
parserOptions: { ecmaVersion: 6 },
- globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+ globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase -- Testing non-CamelCase
errors: [
{
messageId: "notCamelCase",
code: "function foo(a_global_variable) { foo = a_global_variable; }",
options: [{ ignoreGlobals: true }],
parserOptions: { ecmaVersion: 6 },
- globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+ globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase -- Testing non-CamelCase
errors: [
{
messageId: "notCamelCase",
code: "const a_global_variable = foo; bar = a_global_variable",
options: [{ ignoreGlobals: true }],
parserOptions: { ecmaVersion: 6 },
- globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+ globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase -- Testing non-CamelCase
errors: [
{
messageId: "notCamelCase",
code: "bar = a_global_variable; var a_global_variable;",
options: [{ ignoreGlobals: true }],
parserOptions: { ecmaVersion: 6 },
- globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+ globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase -- Testing non-CamelCase
errors: [
{
messageId: "notCamelCase",
code: "var foo = { a_global_variable }",
options: [{ ignoreGlobals: true }],
parserOptions: { ecmaVersion: 6 },
- globals: { a_global_variable: "readonly" }, // eslint-disable-line camelcase
+ globals: { a_global_variable: "readonly" }, // eslint-disable-line camelcase -- Testing non-CamelCase
errors: [
{
messageId: "notCamelCase",
}
]
},
+ {
+ 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'",
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
options: [{ properties: "always" }],
parserOptions: { ecmaVersion: 2020 },
errors: [{ messageId: "notCamelCase", data: { name: "non_camelcase" } }]
+ },
+
+ // class public/private fields, private methods.
+ {
+ code: "class C { snake_case; }",
+ options: [{ properties: "always" }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "notCamelCase", data: { name: "snake_case" } }]
+ },
+ {
+ code: "class C { #snake_case; foo() { this.#snake_case; } }",
+ options: [{ properties: "always" }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "notCamelCasePrivate", data: { name: "snake_case" }, column: 11 }]
+ },
+ {
+ code: "class C { #snake_case() {} }",
+ options: [{ properties: "always" }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "notCamelCasePrivate", data: { name: "snake_case" } }]
}
]
});
{ code: "({ a(){} });", parserOptions: { ecmaVersion: 6 } },
{ code: "class A { foo() { () => this; } }", parserOptions: { ecmaVersion: 6 } },
{ code: "({ a: function () {} });", parserOptions: { ecmaVersion: 6 } },
- { code: "class A { foo() {this} bar() {} }", options: [{ exceptMethods: ["bar"] }], parserOptions: { ecmaVersion: 6 } }
+ { code: "class A { foo() {this} bar() {} }", options: [{ exceptMethods: ["bar"] }], parserOptions: { ecmaVersion: 6 } },
+ { code: "class A { \"foo\"() { } }", options: [{ exceptMethods: ["foo"] }], parserOptions: { ecmaVersion: 6 } },
+ { code: "class A { 42() { } }", options: [{ exceptMethods: ["42"] }], parserOptions: { ecmaVersion: 6 } },
+ { code: "class A { foo = function() {this} }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { foo = () => {this} }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { foo = () => {super.toString} }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { static foo = function() {} }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { static foo = () => {} }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { #bar() {} }", options: [{ exceptMethods: ["#bar"] }], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { foo = function () {} }", options: [{ enforceForClassFields: false }], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { foo = () => {} }", options: [{ enforceForClassFields: false }], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { foo() { return class { [this.foo] = 1 }; } }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { static {} }", parserOptions: { ecmaVersion: 2022 } }
],
invalid: [
{
code: "class A { foo() {} }",
parserOptions: { ecmaVersion: 6 },
errors: [
- { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } }
+ { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } }
]
},
{
code: "class A { foo() {/**this**/} }",
parserOptions: { ecmaVersion: 6 },
errors: [
- { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } }
+ { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } }
]
},
{
code: "class A { foo() {var a = function () {this};} }",
parserOptions: { ecmaVersion: 6 },
errors: [
- { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } }
+ { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } }
]
},
{
code: "class A { foo() {var a = function () {var b = function(){this}};} }",
parserOptions: { ecmaVersion: 6 },
errors: [
- { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } }
+ { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } }
]
},
{
code: "class A { foo() {window.this} }",
parserOptions: { ecmaVersion: 6 },
errors: [
- { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } }
+ { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } }
]
},
{
code: "class A { foo() {that.this = 'this';} }",
parserOptions: { ecmaVersion: 6 },
errors: [
- { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } }
+ { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } }
]
},
{
code: "class A { foo() { () => undefined; } }",
parserOptions: { ecmaVersion: 6 },
errors: [
- { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } }
+ { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } }
]
},
{
options: [{ exceptMethods: ["bar"] }],
parserOptions: { ecmaVersion: 6 },
errors: [
- { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } }
+ { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } }
]
},
{
options: [{ exceptMethods: ["foo"] }],
parserOptions: { ecmaVersion: 6 },
errors: [
- { type: "FunctionExpression", line: 1, column: 34, messageId: "missingThis", data: { name: "method 'hasOwnProperty'" } }
+ { type: "FunctionExpression", line: 1, column: 20, messageId: "missingThis", data: { name: "method 'hasOwnProperty'" } }
]
},
{
options: [{ exceptMethods: ["foo"] }],
parserOptions: { ecmaVersion: 6 },
errors: [
- { type: "FunctionExpression", line: 1, column: 16, messageId: "missingThis", data: { name: "method" } }
+ { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method" } }
+ ]
+ },
+ {
+ code: "class A { #foo() { } foo() {} #bar() {} }",
+ options: [{ exceptMethods: ["#foo"] }],
+ parserOptions: { 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(){} }",
parserOptions: { ecmaVersion: 6 },
errors: [
- { messageId: "missingThis", data: { name: "method 'foo'" }, type: "FunctionExpression", column: 14 },
- { messageId: "missingThis", data: { name: "method 'bar'" }, type: "FunctionExpression", column: 24 },
- { messageId: "missingThis", data: { name: "method '123'" }, type: "FunctionExpression", column: 32 },
- { messageId: "missingThis", data: { name: "method 'baz'" }, type: "FunctionExpression", column: 44 },
- { messageId: "missingThis", data: { name: "method" }, type: "FunctionExpression", column: 52 },
- { messageId: "missingThis", data: { name: "method" }, type: "FunctionExpression", column: 63 },
- { messageId: "missingThis", data: { name: "getter 'quux'" }, type: "FunctionExpression", column: 76 },
- { messageId: "missingThis", data: { name: "setter" }, type: "FunctionExpression", column: 87 },
- { messageId: "missingThis", data: { name: "generator method 'quuux'" }, type: "FunctionExpression", column: 99 }
+ { 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() {} }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "missingThis", data: { name: "method 'foo'" }, column: 11, endColumn: 25 }
+ ]
+ },
+ {
+ code: "class A { foo = () => {} }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "missingThis", data: { name: "method 'foo'" }, column: 11, endColumn: 17 }
+ ]
+ },
+ {
+ code: "class A { #foo = function() {} }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "missingThis", data: { name: "private method #foo" }, column: 11, endColumn: 26 }
+ ]
+ },
+ {
+ code: "class A { #foo = () => {} }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "missingThis", data: { name: "private method #foo" }, column: 11, endColumn: 18 }
+ ]
+ },
+ {
+ code: "class A { #foo() {} }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "missingThis", data: { name: "private method #foo" }, column: 11, endColumn: 15 }
+ ]
+ },
+ {
+ code: "class A { get #foo() {} }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "missingThis", data: { name: "private getter #foo" }, column: 11, endColumn: 19 }
+ ]
+ },
+ {
+ code: "class A { set #foo(x) {} }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "missingThis", data: { name: "private setter #foo" }, column: 11, endColumn: 19 }
+ ]
+ },
+ {
+ code: "class A { foo () { return class { foo = this }; } }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "missingThis", data: { name: "method 'foo'" }, column: 11, endColumn: 15 }
+ ]
+ },
+ {
+ code: "class A { foo () { return function () { foo = this }; } }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "missingThis", data: { name: "method 'foo'" }, column: 11, endColumn: 15 }
+ ]
+ },
+ {
+ code: "class A { foo () { return class { static { this; } } } }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "missingThis", data: { name: "method 'foo'" }, column: 11, endColumn: 15 }
]
}
]
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
+ }
+ }],
+ parserOptions: {
+ ecmaVersion: 6
+ }
+ },
+ {
+ code: "const [\n ,, \n a, \n b, \n] = arr;",
+ options: ["last", {
+ exceptions: {
+ ArrayPattern: false
+ }
+ }],
+ parserOptions: {
+ ecmaVersion: 6
+ }
+ },
+ {
+ code: "const arr = [\n 1 \n , \n ,2 \n]",
+ options: ["first"],
+ parserOptions: {
+ ecmaVersion: 6
+ }
+ },
+ {
+ code: "const arr = [\n ,'fifi' \n]",
+ options: ["first"],
+ parserOptions: {
+ ecmaVersion: 6
+ }
}
+
],
invalid: [
/**
* Create an expected error object
- * @param {string} name The name of the symbol being tested
- * @param {number} complexity The cyclomatic complexity value of the symbol
- * @param {number} max The maximum cyclomatic complexity value of the symbol
- * @returns {Object} The error object
+ * @param {string} name The name of the symbol being tested
+ * @param {number} complexity The cyclomatic complexity value of the symbol
+ * @param {number} max The maximum cyclomatic complexity value of the symbol
+ * @returns {Object} The error object
*/
function makeError(name, complexity, max) {
return {
};
}
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2021 } });
ruleTester.run("complexity", rule, {
{ code: "if (foo) { bar(); }", options: [3] },
{ code: "var a = (x) => {do {'foo';} while (true)}", options: [2], parserOptions: { ecmaVersion: 6 } },
+ // class fields
+ { code: "function foo() { class C { x = a || b; y = c || d; } }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "function foo() { class C { static x = a || b; static y = c || d; } }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "function foo() { class C { x = a || b; y = c || d; } e || f; }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "function foo() { a || b; class C { x = c || d; y = e || f; } }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "function foo() { class C { [x || y] = a || b; } }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { x = a || b; y() { c || d; } z = e || f; }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { x() { a || b; } y = c || d; z() { e || f; } }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { x = (() => { a || b }) || (() => { c || d }) }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { x = () => { a || b }; y = () => { c || d } }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { x = a || (() => { b || c }); }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { x = class { y = a || b; z = c || d; }; }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { x = a || class { y = b || c; z = d || e; }; }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { x; y = a; static z; static q = b; }", options: [1], parserOptions: { ecmaVersion: 2022 } },
+
+ // class static blocks
+ { code: "function foo() { class C { static { a || b; } static { c || d; } } }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "function foo() { a || b; class C { static { c || d; } } }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "function foo() { class C { static { a || b; } } c || d; }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "function foo() { class C { static { a || b; } } class D { static { c || d; } } }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { a || b; } static { c || d; } }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { a || b; } static { c || d; } static { e || f; } }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { () => a || b; c || d; } }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { a || b; () => c || d; } static { c || d; } }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { a } }", options: [1], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { a } static { b } }", options: [1], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { a || b; } } class D { static { c || d; } }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { a || b; } static c = d || e; }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static a = b || c; static { c || d; } }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { a || b; } c = d || e; }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { a = b || c; static { d || e; } }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { a || b; c || d; } }", options: [3], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { if (a || b) c = d || e; } }", options: [4], parserOptions: { ecmaVersion: 2022 } },
+
// object property options
{ code: "function b(x) {}", options: [{ max: 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], parserOptions: { ecmaVersion: 6 }, errors: [makeError("Method 'a'", 2, 1)] },
- { code: "var obj = { a: function b(x) { return x ? 0 : 1; } };", options: [1], errors: [makeError("Method 'b'", 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)]
errors: [makeError("Function 'test'", 21, 20)]
},
+ // class fields
+ {
+ code: "function foo () { a || b; class C { x; } c || d; }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Function 'foo'", 3, 2)]
+ },
+ {
+ code: "function foo () { a || b; class C { x = c; } d || e; }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Function 'foo'", 3, 2)]
+ },
+ {
+ code: "function foo () { a || b; class C { [x || y]; } }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Function 'foo'", 3, 2)]
+ },
+ {
+ code: "function foo () { a || b; class C { [x || y] = c; } }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Function 'foo'", 3, 2)]
+ },
+ {
+ code: "function foo () { class C { [x || y]; } a || b; }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Function 'foo'", 3, 2)]
+ },
+ {
+ code: "function foo () { class C { [x || y] = a; } b || c; }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Function 'foo'", 3, 2)]
+ },
+ {
+ code: "function foo () { class C { [x || y]; [z || q]; } }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Function 'foo'", 3, 2)]
+ },
+ {
+ code: "function foo () { class C { [x || y] = a; [z || q] = b; } }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Function 'foo'", 3, 2)]
+ },
+ {
+ code: "function foo () { a || b; class C { x = c || d; } e || f; }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Function 'foo'", 3, 2)]
+ },
+ {
+ code: "class C { x(){ a || b; } y = c || d || e; z() { f || g; } }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Class field initializer", 3, 2)]
+ },
+ {
+ code: "class C { x = a || b; y() { c || d || e; } z = f || g; }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Method 'y'", 3, 2)]
+ },
+ {
+ code: "class C { x; y() { c || d || e; } z; }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Method 'y'", 3, 2)]
+ },
+ {
+ code: "class C { x = a || b; }",
+ options: [1],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Class field initializer", 2, 1)]
+ },
+ {
+ code: "(class { x = a || b; })",
+ options: [1],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Class field initializer", 2, 1)]
+ },
+ {
+ code: "class C { static x = a || b; }",
+ options: [1],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Class field initializer", 2, 1)]
+ },
+ {
+ code: "(class { x = a ? b : c; })",
+ options: [1],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Class field initializer", 2, 1)]
+ },
+ {
+ code: "class C { x = a || b || c; }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Class field initializer", 3, 2)]
+ },
+ {
+ code: "class C { x = a || b; y = b || c || d; z = e || f; }",
+ options: [2],
+ parserOptions: { 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],
+ parserOptions: { 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],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Method 'x'", 3, 2)]
+ },
+ {
+ code: "class C { x = (() => a || b || c) || d; }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Arrow function", 3, 2)]
+ },
+ {
+ code: "class C { x = () => a || b || c; y = d || e; }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Method 'x'", 3, 2)]
+ },
+ {
+ code: "class C { x = () => a || b || c; y = d || e || f; }",
+ options: [2],
+ parserOptions: { 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],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ makeError("Method 'x'", 2, 1),
+ makeError("Method 'y'", 2, 1)
+ ]
+ },
+ {
+ code: "class C { x = class { [y || z]; }; }",
+ options: [1],
+ parserOptions: { 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],
+ parserOptions: { 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],
+ parserOptions: { 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],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Function 'foo'", 3, 2)]
+ },
+ {
+ code: "function foo () { a || b; class C { static { c || d; } } e || f; }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Function 'foo'", 3, 2)]
+ },
+ {
+ code: "class C { static { a || b; } }",
+ options: [1],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Class static block", 2, 1)]
+ },
+ {
+ code: "class C { static { a || b || c; } }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Class static block", 3, 2)]
+ },
+ {
+ code: "class C { static { a || b; c || d; } }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Class static block", 3, 2)]
+ },
+ {
+ code: "class C { static { a || b; c || d; e || f; } }",
+ options: [3],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Class static block", 4, 3)]
+ },
+ {
+ code: "class C { static { a || b; c || d; { e || f; } } }",
+ options: [3],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Class static block", 4, 3)]
+ },
+ {
+ code: "class C { static { if (a || b) c = d || e; } }",
+ options: [3],
+ parserOptions: { 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],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Class static block", 4, 3)]
+ },
+ {
+ code: "class C { x(){ a || b; } static { c || d || e; } z() { f || g; } }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Class static block", 3, 2)]
+ },
+ {
+ code: "class C { x = a || b; static { c || d || e; } y = f || g; }",
+ options: [2],
+ parserOptions: { 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],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Class static block", 3, 2)]
+ },
+ {
+ code: "class C { static { a || b; } static(){ c || d || e; } static { f || g; } }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [makeError("Method 'static'", 3, 2)]
+ },
+ {
+ code: "class C { static { a || b; } static static(){ c || d || e; } static { f || g; } }",
+ options: [2],
+ parserOptions: { 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],
+ parserOptions: { 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],
+ parserOptions: { 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],
+ parserOptions: { 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],
+ parserOptions: { 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)] }
]
options: ["always", { enforceForClassMembers: false }],
parserOptions: { ecmaVersion: 6 }
},
+ {
+ code: "class A { [ a ]; }",
+ options: ["never", { enforceForClassMembers: false }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class A { [a]; }",
+ options: ["always", { enforceForClassMembers: false }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
// valid spacing
{
options: ["always", { enforceForClassMembers: true }],
parserOptions: { ecmaVersion: 6 }
},
+ {
+ code: "A = class { [a]; static [a]; [a] = 0; static [a] = 0; }",
+ options: ["never", { enforceForClassMembers: true }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "A = class { [ a ]; static [ a ]; [ a ] = 0; static [ a ] = 0; }",
+ options: ["always", { enforceForClassMembers: true }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
// non-computed
{
options: ["always", { enforceForClassMembers: true }],
parserOptions: { ecmaVersion: 6 }
},
+ {
+ code: "A = class { foo; #a; static #b; #c = 0; static #d = 0; }",
+ options: ["never", { enforceForClassMembers: true }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "A = class { foo; #a; static #b; #c = 0; static #d = 0; }",
+ options: ["always", { enforceForClassMembers: true }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
// handling of parens and comments
{
}
]
},
+ {
+ 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 }],
+ parserOptions: { 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]; [b ]; [c]; [ a] = 0; [b ] = 0; [c] = 0; }",
+ output: "class A { [ a ]; [ b ]; [ c ]; [ a ] = 0; [ b ] = 0; [ c ] = 0; }",
+ options: ["always", { enforceForClassMembers: true }],
+ parserOptions: { 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
{
{
messageId: "missingReturnValue",
data: { name: "Function 'foo'" },
- type: "ReturnStatement"
+ type: "ReturnStatement",
+ line: 1,
+ column: 46,
+ endLine: 1,
+ endColumn: 53
}
]
},
{
messageId: "missingReturnValue",
data: { name: "Arrow function" },
- type: "ReturnStatement"
+ type: "ReturnStatement",
+ line: 1,
+ column: 47,
+ endLine: 1,
+ endColumn: 54
}
]
},
{
messageId: "unexpectedReturnValue",
data: { name: "Function 'foo'" },
- type: "ReturnStatement"
+ type: "ReturnStatement",
+ line: 1,
+ column: 41,
+ endLine: 1,
+ endColumn: 54
}
]
},
{
messageId: "missingReturnValue",
data: { name: "Function" },
- type: "ReturnStatement"
+ type: "ReturnStatement",
+ line: 1,
+ column: 44,
+ endLine: 1,
+ endColumn: 51
}
]
},
{
messageId: "unexpectedReturnValue",
data: { name: "Function" },
- type: "ReturnStatement"
+ type: "ReturnStatement",
+ line: 1,
+ column: 39,
+ endLine: 1,
+ endColumn: 52
}
]
},
{
messageId: "unexpectedReturnValue",
data: { name: "Arrow function" },
- type: "ReturnStatement"
+ type: "ReturnStatement",
+ line: 1,
+ column: 33,
+ endLine: 1,
+ endColumn: 46
}
]
},
messageId: "missingReturnValue",
data: { name: "Function 'foo'" },
type: "ReturnStatement",
- column: 41
+ line: 1,
+ column: 41,
+ endLine: 1,
+ endColumn: 58
}
]
},
messageId: "missingReturnValue",
data: { name: "Function 'foo'" },
type: "ReturnStatement",
- column: 41
+ line: 1,
+ column: 41,
+ endLine: 1,
+ endColumn: 55
}
]
},
messageId: "unexpectedReturnValue",
data: { name: "Function 'foo'" },
type: "ReturnStatement",
- column: 46
+ line: 1,
+ column: 46,
+ endLine: 1,
+ endColumn: 58
}
]
},
messageId: "unexpectedReturnValue",
data: { name: "Function 'foo'" },
type: "ReturnStatement",
- column: 43
+ line: 1,
+ column: 43,
+ endLine: 1,
+ endColumn: 55
}
]
},
{
messageId: "missingReturnValue",
data: { name: "Program" },
- type: "ReturnStatement"
+ type: "ReturnStatement",
+ line: 1,
+ column: 25,
+ endLine: 1,
+ endColumn: 32
}
]
},
messageId: "missingReturn",
data: { name: "function 'foo'" },
type: "FunctionDeclaration",
- column: 10
+ line: 1,
+ column: 10,
+ endLine: 1,
+ endColumn: 13
}
]
},
messageId: "missingReturn",
data: { name: "function '_foo'" },
type: "FunctionDeclaration",
- column: 10
+ line: 1,
+ column: 10,
+ endLine: 1,
+ endColumn: 14
}
]
},
messageId: "missingReturn",
data: { name: "function 'foo'" },
type: "FunctionExpression",
- column: 12
+ line: 1,
+ column: 12,
+ endLine: 1,
+ endColumn: 15
}
]
},
messageId: "missingReturn",
data: { name: "function" },
type: "FunctionExpression",
- column: 3
+ line: 1,
+ column: 3,
+ endLine: 1,
+ endColumn: 11
}
]
},
messageId: "missingReturn",
data: { name: "arrow function" },
type: "ArrowFunctionExpression",
- column: 6
+ line: 1,
+ column: 6,
+ endLine: 1,
+ endColumn: 8
}
]
},
messageId: "missingReturn",
data: { name: "method 'foo'" },
type: "FunctionExpression",
- column: 12
+ line: 1,
+ column: 12,
+ endLine: 1,
+ endColumn: 15
}
]
},
messageId: "missingReturn",
data: { name: "method 'foo'" },
type: "FunctionExpression",
- column: 10
+ line: 1,
+ column: 10,
+ endLine: 1,
+ endColumn: 13
}
]
},
messageId: "missingReturn",
data: { name: "program" },
type: "Program",
- column: 1
+ line: 1,
+ column: 1,
+ endLine: void 0,
+ endColumn: void 0
}
]
},
messageId: "missingReturn",
data: { name: "method 'CapitalizedFunction'" },
type: "FunctionExpression",
- column: 11
+ line: 1,
+ column: 11,
+ endLine: 1,
+ endColumn: 30
}
]
},
messageId: "missingReturn",
data: { name: "method 'constructor'" },
type: "FunctionExpression",
- column: 4
+ line: 1,
+ column: 4,
+ endLine: 1,
+ endColumn: 15
}
]
}
{ RuleTester } = require("../../../lib/rule-tester");
//------------------------------------------------------------------------------
-// Helpers
+// Tests
//------------------------------------------------------------------------------
const ruleTester = new RuleTester();
{
messageId: "missingCurlyAfterCondition",
data: { name: "if" },
- type: "IfStatement"
+ 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
}
]
},
{
messageId: "missingCurlyAfterCondition",
data: { name: "while" },
- type: "WhileStatement"
+ 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
}
]
},
{
messageId: "missingCurlyAfter",
data: { name: "do" },
- type: "DoWhileStatement"
+ 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
}
]
},
{
messageId: "missingCurlyAfter",
data: { name: "for-of" },
- type: "ForOfStatement"
+ 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)}",
+ parserOptions: { 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)}",
+ parserOptions: { 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)}",
+ parserOptions: { 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"],
+ parserOptions: { 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"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: "unexpectedCurlyAfter",
+ data: { name: "do" },
+ type: "DoWhileStatement",
+ line: 1,
+ column: 3,
+ endLine: 1,
+ endColumn: 11
}
]
},
{
messageId: "unexpectedCurlyAfterCondition",
data: { name: "for" },
- type: "ForStatement"
+ 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
}
]
},
{
messageId: "unexpectedCurlyAfterCondition",
data: { name: "if" },
- type: "IfStatement"
+ type: "IfStatement",
+ line: 1,
+ column: 10,
+ endLine: 1,
+ endColumn: 19
}
]
},
{
messageId: "unexpectedCurlyAfterCondition",
data: { name: "while" },
- type: "WhileStatement"
+ type: "WhileStatement",
+ line: 1,
+ column: 13,
+ endLine: 1,
+ endColumn: 22
}
]
},
data: { name: "else" },
type: "IfStatement",
line: 6,
- column: 3
+ column: 8,
+ endLine: 11,
+ endColumn: 2
}
]
},
{
messageId: "unexpectedCurlyAfter",
data: { name: "for-in" },
- type: "ForInStatement"
+ type: "ForInStatement",
+ line: 1,
+ column: 22,
+ endLine: 1,
+ endColumn: 42
}
]
},
{
messageId: "missingCurlyAfterCondition",
data: { name: "if" },
- type: "IfStatement"
+ 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: "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)",
{
messageId: "missingCurlyAfter",
data: { name: "do" },
- type: "DoWhileStatement"
+ 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
}
]
},
{
messageId: "missingCurlyAfter",
data: { name: "for-of" },
- type: "ForOfStatement"
+ type: "ForOfStatement",
+ line: 2,
+ column: 2,
+ endLine: 2,
+ endColumn: 18
}
]
},
{
messageId: "unexpectedCurlyAfterCondition",
data: { name: "for" },
- type: "ForStatement"
+ 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
}
]
},
{
messageId: "missingCurlyAfter",
data: { name: "for-in" },
- type: "ForInStatement"
+ type: "ForInStatement",
+ line: 2,
+ column: 2,
+ endLine: 3,
+ endColumn: 22
}
]
},
{
messageId: "unexpectedCurlyAfter",
data: { name: "else" },
- type: "IfStatement"
+ type: "IfStatement",
+ line: 1,
+ column: 23,
+ endLine: 1,
+ endColumn: 33
}
]
},
}
]
},
+ {
+ 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)",
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" }]
+ 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
+ }]
},
{
{ 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",
+ parserOptions: { ecmaVersion: 6 },
+ errors: [{
+ data: { name: "for-of" },
+ type: "ForOfStatement",
+ messageId: "missingCurlyAfter",
+ line: 3,
+ column: 1,
+ endLine: 3,
+ endColumn: 6
+ }]
}
]
});
*/
"use strict";
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
const rule = require("../../../lib/rules/default-param-last");
const { RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const SHOULD_BE_LAST = "shouldBeLast";
const ruleTester = new RuleTester({
code: "obj?.[\nkey]",
options: ["property"],
parserOptions: { ecmaVersion: 2020 }
+ },
+
+ // Private properties
+ {
+ code: "class C { #a; foo() { this.\n#a; } }",
+ options: ["object"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { #a; foo() { this\n.#a; } }",
+ options: ["property"],
+ parserOptions: { ecmaVersion: 2022 }
}
],
invalid: [
options: ["property"],
parserOptions: { 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"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "expectedDotAfterObject" }]
+ },
+ {
+ code: "class C { #a; foo() { this.\n#a; } }",
+ output: "class C { #a; foo() { this\n.#a; } }",
+ options: ["property"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "expectedDotBeforeProperty" }]
}
]
});
/**
* Quote a string in "double quotes" because it’s painful
* with a double-quoted string literal
- * @param {string} str The string to quote
- * @returns {string} `"${str}"`
+ * @param {string} str The string to quote
+ * @returns {string} `"${str}"`
*/
function q(str) {
return `"${str}"`;
}
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
ruleTester.run("dot-notation", rule, {
valid: [
"a.b;",
"a[undefined];",
"a[void 0];",
"a[b()];",
- { code: "a[/(?<zero>0)/];", parserOptions: { ecmaVersion: 2018 } }
+ { code: "a[/(?<zero>0)/];", parserOptions: { ecmaVersion: 2018 } },
+ { code: "class C { foo() { this['#a'] } }", parserOptions: { ecmaVersion: 2022 } },
+ {
+ code: "class C { #in; foo() { this.#in; } }",
+ options: [{ allowKeywords: false }],
+ parserOptions: { ecmaVersion: 2022 }
+ }
],
invalid: [
{
{
code: "var a = 123;",
output: "var a = 123;\n",
- errors: [{ messageId: "missing", type: "Program" }]
+ 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" }]
+ 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" }]
+ 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" }]
+ 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" }]
+ 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" }]
+ 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" }]
+ 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" }]
+ 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" }]
+ 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" }]
+ 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" }]
+ 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" }]
+ errors: [{
+ messageId: "missing",
+ type: "Program",
+ line: 2,
+ column: 4,
+ endLine: void 0,
+ endColumn: void 0
+ }]
}
]
});
const { RuleTester } = require("../../../lib/rule-tester");
//------------------------------------------------------------------------------
-// Helpers
+// Tests
//------------------------------------------------------------------------------
const ruleTester = new RuleTester();
{
code: "foo({ value: function value() {} })",
options: ["always", { considerPropertyDescriptor: true }]
+ },
+
+ // class fields, private names are ignored
+ {
+ code: "class C { x = function () {}; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { x = function () {}; }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { 'x' = function () {}; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { 'x' = function () {}; }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { #x = function () {}; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { #x = function () {}; }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { [x] = function () {}; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { [x] = function () {}; }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { ['x'] = function () {}; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { ['x'] = function () {}; }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { x = function x() {}; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { x = function y() {}; }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { 'x' = function x() {}; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { 'x' = function y() {}; }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { #x = function x() {}; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { #x = function x() {}; }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { #x = function y() {}; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { #x = function y() {}; }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { [x] = function x() {}; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { [x] = function x() {}; }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { [x] = function y() {}; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { [x] = function y() {}; }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { ['x'] = function x() {}; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { ['x'] = function y() {}; }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { 'xy ' = function foo() {}; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { 'xy ' = function xy() {}; }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { ['xy '] = function foo() {}; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { ['xy '] = function xy() {}; }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { 1 = function x0() {}; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { 1 = function x1() {}; }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { [1] = function x0() {}; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { [1] = function x1() {}; }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { [f()] = function g() {}; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { [f()] = function f() {}; }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static x = function x() {}; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static x = function y() {}; }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { x = (function y() {})(); }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { x = (function x() {})(); }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "(class { x = function x() {}; })",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "(class { x = function y() {}; })",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { #x; foo() { this.#x = function x() {}; } }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { #x; foo() { this.#x = function x() {}; } }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { #x; foo() { this.#x = function y() {}; } }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { #x; foo() { this.#x = function y() {}; } }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { #x; foo() { a.b.#x = function x() {}; } }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { #x; foo() { a.b.#x = function x() {}; } }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { #x; foo() { a.b.#x = function y() {}; } }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { #x; foo() { a.b.#x = function y() {}; } }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
}
],
invalid: [
errors: [
{ messageId: "notMatchProperty", data: { funcName: "bar", name: "bar" } }
]
+ },
+
+ // class fields
+ {
+ code: "class C { x = function y() {}; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "matchProperty", data: { funcName: "y", name: "x" } }
+ ]
+ },
+ {
+ code: "class C { x = function x() {}; }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "notMatchProperty", data: { funcName: "x", name: "x" } }
+ ]
+ },
+ {
+ code: "class C { 'x' = function y() {}; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "matchProperty", data: { funcName: "y", name: "x" } }
+ ]
+ },
+ {
+ code: "class C { 'x' = function x() {}; }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "notMatchProperty", data: { funcName: "x", name: "x" } }
+ ]
+ },
+ {
+ code: "class C { ['x'] = function y() {}; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "matchProperty", data: { funcName: "y", name: "x" } }
+ ]
+ },
+ {
+ code: "class C { ['x'] = function x() {}; }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "notMatchProperty", data: { funcName: "x", name: "x" } }
+ ]
+ },
+ {
+ code: "class C { static x = function y() {}; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "matchProperty", data: { funcName: "y", name: "x" } }
+ ]
+ },
+ {
+ code: "class C { static x = function x() {}; }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "notMatchProperty", data: { funcName: "x", name: "x" } }
+ ]
+ },
+ {
+ code: "(class { x = function y() {}; })",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "matchProperty", data: { funcName: "y", name: "x" } }
+ ]
+ },
+ {
+ code: "(class { x = function x() {}; })",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "notMatchProperty", data: { funcName: "x", name: "x" } }
+ ]
}
]
});
code: "(function*() {}())",
options: ["as-needed", { generators: "never" }],
parserOptions: { ecmaVersion: 6 }
+ },
+
+ // class fields
+ {
+ code: "class C { foo = function() {}; }",
+ options: ["as-needed"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { [foo] = function() {}; }",
+ options: ["as-needed"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { #foo = function() {}; }",
+ options: ["as-needed"],
+ parserOptions: { ecmaVersion: 2022 }
}
],
invalid: [
column: 15,
endColumn: 28
}]
+ },
+
+ // class fields
+ {
+ code: "class C { foo = function() {} }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "unnamed",
+ data: { name: "method 'foo'" },
+ column: 11,
+ endColumn: 25
+ }]
+ },
+ {
+ code: "class C { [foo] = function() {} }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "unnamed",
+ data: { name: "method" },
+ column: 11,
+ endColumn: 27
+ }]
+ },
+ {
+ code: "class C { #foo = function() {} }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "unnamed",
+ data: { name: "private method #foo" },
+ column: 11,
+ endColumn: 26
+ }]
+ },
+ {
+ code: "class C { foo = bar(function() {}) }",
+ options: ["as-needed"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "unnamed",
+ data: { name: "function" },
+ column: 21,
+ endColumn: 29
+ }]
+ },
+ {
+ code: "class C { foo = function bar() {} }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "named",
+ data: { name: "method 'foo'" },
+ column: 11,
+ endColumn: 29
+ }]
}
]
});
// Tests
//------------------------------------------------------------------------------
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } });
const expectedError = { messageId: "expected", data: { name: "getter 'bar'" } };
const expectedAlwaysError = { messageId: "expectedAlways", data: { name: "getter 'bar'" } };
const options = [{ allowImplicit: true }];
"var foo = { bar: function(){return;} };",
"var foo = { bar: function(){return true;} };",
"var foo = { get: function () {} }",
- "var foo = { get: () => {}};"
+ "var foo = { get: () => {}};",
+ "class C { get; foo() {} }"
],
invalid: [
code: "Object.defineProperty(foo, 'bar', { get: function getfoo (){}});",
errors: [{
messageId: "expected",
- data: { name: "method 'getfoo'" },
+ data: { name: "method 'get'" },
line: 1,
column: 37,
endLine: 1,
code: "Object.defineProperty(foo, 'bar', { get: () => {}});",
errors: [{
messageId: "expected",
- data: { name: "arrow function 'get'" },
+ data: { name: "method 'get'" },
line: 1,
- column: 45,
+ column: 37,
endLine: 1,
- endColumn: 47
+ endColumn: 42
}]
},
{ code: "Object.defineProperty(foo, \"bar\", { get: function (){if(bar) {return true;}}});", errors: [{ messageId: "expectedAlways" }] },
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester();
const valid = [
// Tests
//------------------------------------------------------------------------------
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } });
ruleTester.run("grouped-accessor-pairs", rule, {
valid: [
"({ 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){} })"
+ "(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"]
+ }
],
invalid: [
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 }]
+ },
// basic ordering tests with full messages
{
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 }]
+ },
// ordering option does not affect the grouping check
{
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){} }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "notGrouped", data: { formerName: "getter 'a'", latterName: "setter 'a'" }, type: "MethodDefinition", column: 24 }]
+ },
// full location tests
{
code: "var foo = { bar: window.baz };",
options: ["window"],
env: { browser: true }
+ },
+
+ // Class fields
+ {
+ code: "class C { camelCase; #camelCase; #camelCase2() {} }",
+ options: ["foo"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { snake_case; #snake_case; #snake_case2() {} }",
+ options: ["foo"],
+ parserOptions: { ecmaVersion: 2022 }
}
],
invalid: [
type: "Identifier"
}
]
+ },
+
+ // Class fields
+ {
+ code: "class C { camelCase; #camelCase; #camelCase2() {} }",
+ options: ["camelCase"],
+ parserOptions: { 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"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ {
+ messageId: "restricted",
+ data: { name: "snake_case" },
+ type: "Identifier"
+ },
+ {
+ messageId: "restrictedPrivate",
+ data: { name: "snake_case" },
+ type: "PrivateIdentifier"
+ }
+ ]
+
}
]
});
const ruleTester = new RuleTester();
const tooShortError = { messageId: "tooShort", type: "Identifier" };
+const tooShortErrorPrivate = { messageId: "tooShortPrivate", type: "PrivateIdentifier" };
const tooLongError = { messageId: "tooLong", type: "Identifier" };
+const tooLongErrorPrivate = { messageId: "tooLongPrivate", type: "PrivateIdentifier" };
ruleTester.run("id-length", rule, {
valid: [
{ 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]"] }] }
+ { code: "var x = 1 ;", options: [{ min: 3, max: 5, exceptionPatterns: ["[x-z]"] }] },
+
+ // Class Fields
+ {
+ code: "class Foo { #xyz() {} }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class Foo { xyz = 1 }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class Foo { #xyz = 1 }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class Foo { #abc() {} }",
+ options: [{ max: 3 }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class Foo { abc = 1 }",
+ options: [{ max: 3 }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class Foo { #abc = 1 }",
+ options: [{ max: 3 }],
+ parserOptions: { ecmaVersion: 2022 }
+ }
],
invalid: [
{ code: "var x = 1;", errors: [tooShortError] },
errors: [
tooShortError
]
+ },
+
+ // Class Fields
+ {
+ code: "class Foo { #x() {} }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ tooShortErrorPrivate
+ ]
+ },
+ {
+ code: "class Foo { x = 1 }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ tooShortError
+ ]
+ },
+ {
+ code: "class Foo { #x = 1 }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ tooShortErrorPrivate
+ ]
+ },
+ {
+ code: "class Foo { #abcdefg() {} }",
+ options: [{ max: 3 }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ tooLongErrorPrivate
+ ]
+ },
+ {
+ code: "class Foo { abcdefg = 1 }",
+ options: [{ max: 3 }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ tooLongError
+ ]
+ },
+ {
+ code: "class Foo { #abcdefg = 1 }",
+ options: [{ max: 3 }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ tooLongErrorPrivate
+ ]
}
]
});
options: ["^[^_]+$", {
properties: false
}]
+ },
+
+ // Class Methods
+ {
+ code: "class x { foo() {} }",
+ options: ["^[^_]+$"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class x { #foo() {} }",
+ options: ["^[^_]+$"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+
+ // Class Fields
+ {
+ code: "class x { _foo = 1; }",
+ options: ["^[^_]+$", {
+ classFields: false
+ }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class x { #_foo = 1; }",
+ options: ["^[^_]+$", {
+ classFields: false
+ }],
+ parserOptions: { ecmaVersion: 2022 }
}
+
],
invalid: [
{
type: "Identifier"
}
]
+ },
+
+ // Class Methods
+ {
+ code: "class x { _foo() {} }",
+ options: ["^[^_]+$"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ {
+ message: "Identifier '_foo' does not match the pattern '^[^_]+$'.",
+ type: "Identifier"
+ }
+ ]
+ },
+ {
+ code: "class x { #_foo() {} }",
+ options: ["^[^_]+$"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ {
+ message: "Identifier '#_foo' does not match the pattern '^[^_]+$'.",
+ type: "PrivateIdentifier"
+ }
+ ]
+ },
+
+ // Class Fields
+ {
+ code: "class x { _foo = 1; }",
+ options: ["^[^_]+$", {
+ classFields: true
+ }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ {
+ message: "Identifier '_foo' does not match the pattern '^[^_]+$'.",
+ type: "Identifier"
+ }
+ ]
+ },
+ {
+ code: "class x { #_foo = 1; }",
+ options: ["^[^_]+$", {
+ classFields: true
+ }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ {
+ message: "Identifier '#_foo' does not match the pattern '^[^_]+$'.",
+ type: "PrivateIdentifier"
+ }
+ ]
}
+
]
});
const path = require("path");
//------------------------------------------------------------------------------
-// Tests
+// Helpers
//------------------------------------------------------------------------------
const fixture = fs.readFileSync(path.join(__dirname, "../../fixtures/rules/indent/indent-invalid-fixture-1.js"), "utf8");
}));
}
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 8, ecmaFeatures: { jsx: true } } });
ruleTester.run("indent", rule, {
ar2){}
`,
options: [2, { FunctionDeclaration: { parameters: "first" }, FunctionExpression: { parameters: "first" } }]
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ foo();
+ bar();
+ }
+ }
+ `,
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ foo();
+ bar();
+ }
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ foo();
+ bar();
+ }
+ }
+ `,
+ options: [4, { StaticBlock: { body: 2 } }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ foo();
+ bar();
+ }
+ }
+ `,
+ options: [4, { StaticBlock: { body: 0 } }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C {
+ \tstatic {
+ \t\tfoo();
+ \t\tbar();
+ \t}
+ }
+ `,
+ options: ["tab"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C {
+ \tstatic {
+ \t\t\tfoo();
+ \t\t\tbar();
+ \t}
+ }
+ `,
+ options: ["tab", { StaticBlock: { body: 2 } }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C {
+ static
+ {
+ foo();
+ bar();
+ }
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ var x,
+ y;
+ }
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C {
+ static
+ {
+ var x,
+ y;
+ }
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ if (foo) {
+ bar;
+ }
+ }
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ {
+ bar;
+ }
+ }
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C {
+ static {}
+
+ static {
+ }
+
+ static
+ {
+ }
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C {
+
+ static {
+ foo;
+ }
+
+ static {
+ bar;
+ }
+
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C {
+
+ x = 1;
+
+ static {
+ foo;
+ }
+
+ y = 2;
+
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C {
+
+ method1(param) {
+ foo;
+ }
+
+ static {
+ bar;
+ }
+
+ method2(param) {
+ foo;
+ }
+
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ function f() {
+ class C {
+ static {
+ foo();
+ bar();
+ }
+ }
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C {
+ method() {
+ foo;
+ }
+ static {
+ bar;
+ }
+ }
+ `,
+ options: [4, { FunctionExpression: { body: 2 }, StaticBlock: { body: 2 } }],
+ parserOptions: { ecmaVersion: 2022 }
}
],
errors: expectedErrors([
[2, 0, 1, "Identifier"]
])
+ },
+ {
+ code: unIndent`
+ class C {
+ field1;
+ static field2;
+ }
+ `,
+ output: unIndent`
+ class C {
+ field1;
+ static field2;
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedErrors([
+ [2, 4, 0, "Identifier"],
+ [3, 4, 0, "Keyword"]
+ ])
+ },
+ {
+ code: unIndent`
+ class C {
+ field1
+ =
+ 0
+ ;
+ static
+ field2
+ =
+ 0
+ ;
+ }
+ `,
+ output: unIndent`
+ class C {
+ field1
+ =
+ 0
+ ;
+ static
+ field2
+ =
+ 0
+ ;
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedErrors([
+ [2, 4, 0, "Identifier"],
+ [3, 8, 0, "Punctuator"],
+ [4, 12, 0, "Numeric"],
+ [5, 12, 0, "Punctuator"],
+ [6, 4, 0, "Keyword"],
+ [7, 8, 0, "Identifier"],
+ [8, 12, 0, "Punctuator"],
+ [9, 16, 0, "Numeric"],
+ [10, 16, 0, "Punctuator"]
+ ])
+ },
+ {
+ code: unIndent`
+ class C {
+ [
+ field1
+ ]
+ =
+ 0
+ ;
+ static
+ [
+ field2
+ ]
+ =
+ 0
+ ;
+ [
+ field3
+ ] =
+ 0;
+ [field4] =
+ 0;
+ }
+ `,
+ output: unIndent`
+ class C {
+ [
+ field1
+ ]
+ =
+ 0
+ ;
+ static
+ [
+ field2
+ ]
+ =
+ 0
+ ;
+ [
+ field3
+ ] =
+ 0;
+ [field4] =
+ 0;
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedErrors([
+ [2, 4, 0, "Punctuator"],
+ [3, 8, 0, "Identifier"],
+ [4, 4, 0, "Punctuator"],
+ [5, 8, 0, "Punctuator"],
+ [6, 12, 0, "Numeric"],
+ [7, 12, 0, "Punctuator"],
+ [8, 4, 0, "Keyword"],
+ [9, 4, 0, "Punctuator"],
+ [10, 8, 0, "Identifier"],
+ [11, 4, 0, "Punctuator"],
+ [12, 8, 0, "Punctuator"],
+ [13, 12, 0, "Numeric"],
+ [14, 12, 0, "Punctuator"],
+ [15, 4, 0, "Punctuator"],
+ [16, 8, 0, "Identifier"],
+ [17, 4, 0, "Punctuator"],
+ [18, 8, 0, "Numeric"],
+ [19, 4, 0, "Punctuator"],
+ [20, 8, 0, "Numeric"]
+ ])
+ },
+ {
+ code: unIndent`
+ class C {
+ field1 = (
+ foo
+ + bar
+ );
+ }
+ `,
+ output: unIndent`
+ class C {
+ field1 = (
+ foo
+ + bar
+ );
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedErrors([
+ [2, 4, 0, "Identifier"],
+ [3, 8, 0, "Identifier"],
+ [5, 4, 0, "Punctuator"]
+ ])
+ },
+ {
+ code: unIndent`
+ class C {
+ #aaa
+ foo() {
+ return this.#aaa
+ }
+ }
+ `,
+ output: unIndent`
+ class C {
+ #aaa
+ foo() {
+ return this.#aaa
+ }
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedErrors([
+ [2, 4, 0, "PrivateIdentifier"],
+ [3, 4, 0, "Identifier"],
+ [4, 8, 0, "Keyword"],
+ [5, 4, 0, "Punctuator"]
+ ])
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ foo();
+ bar();
+ }
+ }
+ `,
+ output: unIndent`
+ class C {
+ static {
+ foo();
+ bar();
+ }
+ }
+ `,
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedErrors([
+ [2, 2, 0, "Keyword"],
+ [3, 4, 0, "Identifier"],
+ [4, 4, 0, "Identifier"],
+ [5, 2, 0, "Punctuator"]
+ ])
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ foo();
+ bar();
+ }
+ }
+ `,
+ output: unIndent`
+ class C {
+ static {
+ foo();
+ bar();
+ }
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedErrors([
+ [2, 4, 0, "Keyword"],
+ [3, 8, 0, "Identifier"],
+ [4, 8, 0, "Identifier"],
+ [5, 4, 0, "Punctuator"]
+ ])
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ foo();
+ bar();
+ }
+ }
+ `,
+ output: unIndent`
+ class C {
+ static {
+ foo();
+ bar();
+ }
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedErrors([
+ [2, 4, 8, "Keyword"],
+ [3, 8, 4, "Identifier"],
+ [4, 8, 0, "Identifier"],
+ [5, 4, 8, "Punctuator"]
+ ])
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ foo();
+ bar();
+ }
+ }
+ `,
+ output: unIndent`
+ class C {
+ static {
+ foo();
+ bar();
+ }
+ }
+ `,
+ options: [4, { StaticBlock: { body: 2 } }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedErrors([
+ [2, 4, 0, "Keyword"],
+ [3, 12, 0, "Identifier"],
+ [4, 12, 0, "Identifier"],
+ [5, 4, 0, "Punctuator"]
+ ])
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ foo();
+ bar();
+ }
+ }
+ `,
+ output: unIndent`
+ class C {
+ static {
+ foo();
+ bar();
+ }
+ }
+ `,
+ options: [4, { StaticBlock: { body: 0 } }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedErrors([
+ [2, 4, 0, "Keyword"],
+ [3, 4, 0, "Identifier"],
+ [4, 4, 0, "Identifier"],
+ [5, 4, 0, "Punctuator"]
+ ])
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ foo();
+ bar();
+ }
+ }
+ `,
+ output: unIndent`
+ class C {
+ \tstatic {
+ \t\tfoo();
+ \t\tbar();
+ \t}
+ }
+ `,
+ options: ["tab"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedErrors("tab", [
+ [2, 1, 0, "Keyword"],
+ [3, 2, 0, "Identifier"],
+ [4, 2, 0, "Identifier"],
+ [5, 1, 0, "Punctuator"]
+ ])
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ foo();
+ bar();
+ }
+ }
+ `,
+ output: unIndent`
+ class C {
+ \tstatic {
+ \t\t\tfoo();
+ \t\t\tbar();
+ \t}
+ }
+ `,
+ options: ["tab", { StaticBlock: { body: 2 } }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedErrors("tab", [
+ [2, 1, 0, "Keyword"],
+ [3, 3, 0, "Identifier"],
+ [4, 3, 0, "Identifier"],
+ [5, 1, 0, "Punctuator"]
+ ])
+ },
+ {
+ code: unIndent`
+ class C {
+ static
+ {
+ foo();
+ bar();
+ }
+ }
+ `,
+ output: unIndent`
+ class C {
+ static
+ {
+ foo();
+ bar();
+ }
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedErrors([
+ [2, 4, 0, "Keyword"],
+ [3, 4, 0, "Punctuator"],
+ [4, 8, 0, "Identifier"],
+ [5, 8, 0, "Identifier"],
+ [6, 4, 0, "Punctuator"]
+ ])
+ },
+ {
+ code: unIndent`
+ class C {
+ static
+ {
+ foo();
+ bar();
+ }
+ }
+ `,
+ output: unIndent`
+ class C {
+ static
+ {
+ foo();
+ bar();
+ }
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedErrors([
+ [3, 4, 8, "Punctuator"],
+ [6, 4, 8, "Punctuator"]
+ ])
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ var x,
+ y;
+ }
+ }
+ `,
+ output: unIndent`
+ class C {
+ static {
+ var x,
+ y;
+ }
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedErrors([
+ [2, 4, 0, "Keyword"],
+ [3, 8, 0, "Keyword"],
+ [4, 12, 0, "Identifier"],
+ [5, 4, 0, "Punctuator"]
+ ])
+ },
+ {
+ code: unIndent`
+ class C {
+ static
+ {
+ var x,
+ y;
+ }
+ }
+ `,
+ output: unIndent`
+ class C {
+ static
+ {
+ var x,
+ y;
+ }
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedErrors([
+ [2, 4, 0, "Keyword"],
+ [3, 4, 0, "Punctuator"],
+ [4, 8, 0, "Keyword"],
+ [5, 12, 0, "Identifier"],
+ [6, 4, 0, "Punctuator"]
+ ])
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ if (foo) {
+ bar;
+ }
+ }
+ }
+ `,
+ output: unIndent`
+ class C {
+ static {
+ if (foo) {
+ bar;
+ }
+ }
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedErrors([
+ [2, 4, 0, "Keyword"],
+ [3, 8, 0, "Keyword"],
+ [4, 12, 0, "Identifier"],
+ [5, 8, 0, "Punctuator"],
+ [6, 4, 0, "Punctuator"]
+ ])
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ {
+ bar;
+ }
+ }
+ }
+ `,
+ output: unIndent`
+ class C {
+ static {
+ {
+ bar;
+ }
+ }
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedErrors([
+ [2, 4, 0, "Keyword"],
+ [3, 8, 0, "Punctuator"],
+ [4, 12, 0, "Identifier"],
+ [5, 8, 0, "Punctuator"],
+ [6, 4, 0, "Punctuator"]
+ ])
+ },
+ {
+ code: unIndent`
+ class C {
+ static {}
+
+ static {
+ }
+
+ static
+ {
+ }
+ }
+ `,
+ output: unIndent`
+ class C {
+ static {}
+
+ static {
+ }
+
+ static
+ {
+ }
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedErrors([
+ [2, 4, 0, "Keyword"],
+ [4, 4, 0, "Keyword"],
+ [5, 4, 0, "Punctuator"],
+ [7, 4, 0, "Keyword"],
+ [8, 4, 0, "Punctuator"],
+ [9, 4, 0, "Punctuator"]
+ ])
+ },
+ {
+ code: unIndent`
+ class C {
+
+ static {
+ foo;
+ }
+
+ static {
+ bar;
+ }
+
+ }
+ `,
+ output: unIndent`
+ class C {
+
+ static {
+ foo;
+ }
+
+ static {
+ bar;
+ }
+
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedErrors([
+ [3, 4, 0, "Keyword"],
+ [4, 8, 4, "Identifier"],
+ [5, 4, 0, "Punctuator"],
+ [7, 4, 0, "Keyword"],
+ [8, 8, 4, "Identifier"],
+ [9, 4, 0, "Punctuator"]
+ ])
+ },
+ {
+ code: unIndent`
+ class C {
+
+ x = 1;
+
+ static {
+ foo;
+ }
+
+ y = 2;
+
+ }
+ `,
+ output: unIndent`
+ class C {
+
+ x = 1;
+
+ static {
+ foo;
+ }
+
+ y = 2;
+
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedErrors([
+ [3, 4, 0, "Identifier"],
+ [5, 4, 0, "Keyword"],
+ [6, 8, 4, "Identifier"],
+ [7, 4, 0, "Punctuator"],
+ [9, 4, 0, "Identifier"]
+ ])
+ },
+ {
+ code: unIndent`
+ class C {
+
+ method1(param) {
+ foo;
+ }
+
+ static {
+ bar;
+ }
+
+ method2(param) {
+ foo;
+ }
+
+ }
+ `,
+ output: unIndent`
+ class C {
+
+ method1(param) {
+ foo;
+ }
+
+ static {
+ bar;
+ }
+
+ method2(param) {
+ foo;
+ }
+
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedErrors([
+ [3, 4, 0, "Identifier"],
+ [4, 8, 4, "Identifier"],
+ [5, 4, 0, "Punctuator"],
+ [7, 4, 0, "Keyword"],
+ [8, 8, 4, "Identifier"],
+ [9, 4, 0, "Punctuator"],
+ [11, 4, 0, "Identifier"],
+ [12, 8, 4, "Identifier"],
+ [13, 4, 0, "Punctuator"]
+ ])
+ },
+ {
+ code: unIndent`
+ function f() {
+ class C {
+ static {
+ foo();
+ bar();
+ }
+ }
+ }
+ `,
+ output: unIndent`
+ function f() {
+ class C {
+ static {
+ foo();
+ bar();
+ }
+ }
+ }
+ `,
+ options: [4],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedErrors([
+ [2, 4, 0, "Keyword"],
+ [3, 8, 0, "Keyword"],
+ [4, 12, 0, "Identifier"],
+ [5, 12, 0, "Identifier"],
+ [6, 8, 0, "Punctuator"],
+ [7, 4, 0, "Punctuator"]
+ ])
+ },
+ {
+ code: unIndent`
+ class C {
+ method() {
+ foo;
+ }
+ static {
+ bar;
+ }
+ }
+ `,
+ output: unIndent`
+ class C {
+ method() {
+ foo;
+ }
+ static {
+ bar;
+ }
+ }
+ `,
+ options: [4, { FunctionExpression: { body: 2 }, StaticBlock: { body: 2 } }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedErrors([
+ [2, 4, 0, "Identifier"],
+ [3, 12, 0, "Identifier"],
+ [4, 4, 0, "Punctuator"],
+ [5, 4, 0, "Keyword"],
+ [6, 12, 0, "Identifier"],
+ [7, 4, 0, "Punctuator"]
+ ])
}
]
});
const rule = require("../../../lib/rules/init-declarations"),
{ RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester();
ruleTester.run("init-declarations", rule, {
const rule = require("../../../lib/rules/jsx-quotes"),
{ RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } });
ruleTester.run("jsx-quotes", rule, {
const rule = require("../../../lib/rules/key-spacing"),
{ RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester();
ruleTester.run("key-spacing", rule, {
const parser = require("../../fixtures/fixture-parser"),
rule = require("../../../lib/rules/keyword-spacing"),
- { RuleTester } = require("../../../lib/rule-tester");
+ { RuleTester } = require("../../../lib/rule-tester"),
+ fixtureParser = require("../../fixtures/fixture-parser");
//------------------------------------------------------------------------------
// Helpers
// not conflict with `space-infix-ops`
{ code: "async function wrap() { a =await a }", parserOptions: { ecmaVersion: 8 } },
{ code: "async function wrap() { a = await a }", options: [NEITHER], parserOptions: { ecmaVersion: 8 } },
+ { code: "async function wrap() { a+await a }", parserOptions: { ecmaVersion: 8 } },
+ { code: "async function wrap() { a + await a }", options: [NEITHER], parserOptions: { ecmaVersion: 8 } },
+ { code: "async function wrap() { a<await a }", parserOptions: { ecmaVersion: 8 } },
+ { code: "async function wrap() { a < await a }", options: [NEITHER], parserOptions: { ecmaVersion: 8 } },
+ { code: "async function wrap() { a>await a }", parserOptions: { ecmaVersion: 8 } },
+ { code: "async function wrap() { a > await a }", options: [NEITHER], parserOptions: { ecmaVersion: 8 } },
// not conflict with `space-unary-ops`
{ code: "async function wrap() { !await'a' }", parserOptions: { ecmaVersion: 8 } },
// not conflict with `space-infix-ops`
{ code: "a =class {}", parserOptions: { ecmaVersion: 6 } },
{ code: "a = class{}", options: [NEITHER], parserOptions: { ecmaVersion: 6 } },
+ { code: "a+class {}", parserOptions: { ecmaVersion: 6 } },
+ { code: "a + class{}", options: [NEITHER], parserOptions: { ecmaVersion: 6 } },
+ { code: "a<class {}", parserOptions: { ecmaVersion: 6 } },
+ { code: "a < class{}", options: [NEITHER], parserOptions: { ecmaVersion: 6 } },
+ { code: "a>class {}", parserOptions: { ecmaVersion: 6 } },
+ { code: "a > class{}", options: [NEITHER], parserOptions: { ecmaVersion: 6 } },
// not conflict with `space-unary-ops`
{ code: "!class {}", parserOptions: { ecmaVersion: 6 } },
{ code: "<Foo onClick={class {}} />", parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } },
{ code: "<Foo onClick={ class{}} />", options: [NEITHER], parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } },
+ // private names
+ { code: "class C {\n#x;\nfoo() {\nfor (this.#x of bar){}}}", options: [{ before: false }], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C {\n#x;\nfoo() {\nfor (this.#x in bar){}}}", options: [{ before: false }], parserOptions: { ecmaVersion: 2022 } },
+
//----------------------------------------------------------------------
// const
//----------------------------------------------------------------------
// not conflict with `space-infix-ops`
"a =delete foo.a",
{ code: "a = delete foo.a", options: [NEITHER] },
+ "a+delete foo.a",
+ { code: "a + delete foo.a", options: [NEITHER] },
+ "a<delete foo.a",
+ { code: "a < delete foo.a", options: [NEITHER] },
+ "a>delete foo.a",
+ { code: "a > delete foo.a", options: [NEITHER] },
// not conflict with `space-unary-ops`
"!delete(foo.a)",
// not conflict with `space-infix-ops`
"a =function() {}",
{ code: "a = function() {}", options: [NEITHER] },
+ "a+function() {}",
+ { code: "a + function() {}", options: [NEITHER] },
+ "a<function() {}",
+ { code: "a < function() {}", options: [NEITHER] },
+ "a>function() {}",
+ { code: "a > function() {}", options: [NEITHER] },
// not conflict with `space-unary-ops`
"!function() {}",
{ code: "class A { a() {} get [b]() {} }", options: [override("get", BOTH)], parserOptions: { ecmaVersion: 6 } },
{ code: "({ get[b]() {} })", options: [override("get", NEITHER)], parserOptions: { ecmaVersion: 6 } },
{ code: "class A { a() {}get[b]() {} }", options: [override("get", NEITHER)], parserOptions: { ecmaVersion: 6 } },
+ { code: "class A { a; get #b() {} }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { a;get#b() {} }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } },
// not conflict with `comma-spacing`
{ code: "({ a,get [b]() {} })", parserOptions: { ecmaVersion: 6 } },
{ code: "({ a, get[b]() {} })", options: [NEITHER], parserOptions: { ecmaVersion: 6 } },
+ // not conflict with `semi-spacing`
+ { code: "class A { ;get #b() {} }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { ; get#b() {} }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } },
+
//----------------------------------------------------------------------
// if
//----------------------------------------------------------------------
// not conflict with `space-infix-ops`
"a =new foo()",
{ code: "a = new foo()", options: [NEITHER] },
+ "a+new foo()",
+ { code: "a + new foo()", options: [NEITHER] },
+ "a<new foo()",
+ { code: "a < new foo()", options: [NEITHER] },
+ "a>new foo()",
+ { code: "a > new foo()", options: [NEITHER] },
// not conflict with `space-unary-ops`
"!new(foo)()",
//----------------------------------------------------------------------
"function foo() { {} return +a }",
+ {
+ code: "function foo() { return <p/>; }",
+ parserOptions: { ecmaFeatures: { jsx: true } }
+ },
{ code: "function foo() { {}return+a }", options: [NEITHER] },
+ {
+ code: "function foo() { return<p/>; }",
+ options: [{ after: false }],
+ parserOptions: { ecmaFeatures: { jsx: true } }
+ },
{ code: "function foo() { {} return +a }", options: [override("return", BOTH)] },
{ code: "function foo() { {}return+a }", options: [override("return", NEITHER)] },
"function foo() {\nreturn\n}",
{ code: "class A { a() {} set [b](value) {} }", options: [override("set", BOTH)], parserOptions: { ecmaVersion: 6 } },
{ code: "({ set[b](value) {} })", options: [override("set", NEITHER)], parserOptions: { ecmaVersion: 6 } },
{ code: "class A { a() {}set[b](value) {} }", options: [override("set", NEITHER)], parserOptions: { ecmaVersion: 6 } },
+ { code: "class A { a; set #b(value) {} }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { a;set#b(value) {} }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } },
// not conflict with `comma-spacing`
{ code: "({ a,set [b](value) {} })", parserOptions: { ecmaVersion: 6 } },
{ code: "({ a, set[b](value) {} })", options: [NEITHER], parserOptions: { ecmaVersion: 6 } },
+ // not conflict with `semi-spacing`
+ { code: "class A { ;set #b(value) {} }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { ; set#b(value) {} }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } },
+
//----------------------------------------------------------------------
// static
//----------------------------------------------------------------------
{ code: "class A { a() {}static[b]() {} }", options: [NEITHER], parserOptions: { ecmaVersion: 6 } },
{ code: "class A { a() {} static [b]() {} }", options: [override("static", BOTH)], parserOptions: { ecmaVersion: 6 } },
{ code: "class A { a() {}static[b]() {} }", options: [override("static", NEITHER)], parserOptions: { ecmaVersion: 6 } },
+ { code: "class A { a; static [b]; }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { a;static[b]; }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { a; static #b; }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { a;static#b; }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { a() {} static {} }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { a() {}static{} }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { a() {} static {} }", options: [override("static", BOTH)], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { a() {}static{} }", options: [override("static", NEITHER)], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { a() {}\nstatic\n{} }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } },
// not conflict with `generator-star-spacing`
{ code: "class A { static* [a]() {} }", parserOptions: { ecmaVersion: 6 } },
// not conflict with `semi-spacing`
{ code: "class A { ;static a() {} }", parserOptions: { ecmaVersion: 6 } },
{ code: "class A { ; static a() {} }", options: [NEITHER], parserOptions: { ecmaVersion: 6 } },
+ { code: "class A { ;static a; }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { ; static a ; }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { ;static {} }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { ; static{} }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } },
//----------------------------------------------------------------------
// super
// not conflict with `space-infix-ops`
{ code: "class A extends B { constructor() { b =super() } }", parserOptions: { ecmaVersion: 6 } },
{ code: "class A extends B { constructor() { b = super() } }", options: [NEITHER], parserOptions: { ecmaVersion: 6 } },
+ { code: "class A extends B { constructor() { b+super() } }", parserOptions: { ecmaVersion: 6 } },
+ { code: "class A extends B { constructor() { b + super() } }", options: [NEITHER], parserOptions: { ecmaVersion: 6 } },
+ { code: "class A extends B { constructor() { b<super() } }", parserOptions: { ecmaVersion: 6 } },
+ { code: "class A extends B { constructor() { b < super() } }", options: [NEITHER], parserOptions: { ecmaVersion: 6 } },
+ { code: "class A extends B { constructor() { b>super() } }", parserOptions: { ecmaVersion: 6 } },
+ { code: "class A extends B { constructor() { b > super() } }", options: [NEITHER], parserOptions: { ecmaVersion: 6 } },
// not conflict with `space-unary-ops`
{ code: "class A extends B { constructor() { !super() } }", parserOptions: { ecmaVersion: 6 } },
{ code: "{} this[a]", options: [override("this", BOTH)] },
{ code: "{}this[a]", options: [override("this", NEITHER)] },
+ {
+ code: "<Thing> this.blah",
+ parser: fixtureParser("keyword-spacing", "prefix-cast-operator-space")
+ },
+ {
+ code: "<Thing>this.blah",
+ options: [override("this", { before: false })],
+ parser: fixtureParser("keyword-spacing", "prefix-cast-operator-no-space")
+ },
+
// not conflict with `array-bracket-spacing`
"[this]",
{ code: "[ this ]", options: [NEITHER] },
// not conflict with `space-infix-ops`
"a =this",
{ code: "a = this", options: [NEITHER] },
+ "a+this",
+ { code: "a + this", options: [NEITHER] },
+ "a<this",
+ { code: "a < this", options: [NEITHER] },
+ "a>this",
+ { code: "a > this", options: [NEITHER] },
+ "this+a",
+ { code: "this + a", options: [NEITHER] },
+ "this<a",
+ { code: "this < a", options: [NEITHER] },
+ "this>a",
+ { code: "this > a", options: [NEITHER] },
// not conflict with `space-unary-ops`
"!this",
// not conflict with `space-infix-ops`
"a =typeof foo",
{ code: "a = typeof foo", options: [NEITHER] },
+ "a+typeof foo",
+ { code: "a + typeof foo", options: [NEITHER] },
+ "a<typeof foo",
+ { code: "a < typeof foo", options: [NEITHER] },
+ "a>typeof foo",
+ { code: "a > typeof foo", options: [NEITHER] },
// not conflict with `space-unary-ops`
"!typeof+foo",
// not conflict with `space-infix-ops`
"a =void foo",
{ code: "a = void foo", options: [NEITHER] },
+ "a+void foo",
+ { code: "a + void foo", options: [NEITHER] },
+ "a<void foo",
+ { code: "a < void foo", options: [NEITHER] },
+ "a>void foo",
+ { code: "a > void foo", options: [NEITHER] },
// not conflict with `space-unary-ops`
"!void+foo",
parserOptions: { ecmaVersion: 6 },
errors: unexpectedBeforeAndAfter("get")
},
+ {
+ code: "class A { a;get#b() {} }",
+ output: "class A { a;get #b() {} }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedAfter("get")
+ },
+ {
+ code: "class A { a; get #b() {} }",
+ output: "class A { a; get#b() {} }",
+ options: [NEITHER],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: unexpectedAfter("get")
+ },
//----------------------------------------------------------------------
// if
output: "function foo() { {} return +a }",
errors: expectedBeforeAndAfter("return")
},
+ {
+ code: "function foo() { return<p/>; }",
+ output: "function foo() { return <p/>; }",
+ parserOptions: { ecmaFeatures: { jsx: true } },
+ errors: expectedAfter("return")
+ },
{
code: "function foo() { {} return +a }",
output: "function foo() { {}return+a }",
options: [NEITHER],
errors: unexpectedBeforeAndAfter("return")
},
+ {
+ code: "function foo() { return <p/>; }",
+ output: "function foo() { return<p/>; }",
+ options: [{ after: false }],
+ parserOptions: { ecmaFeatures: { jsx: true } },
+ errors: unexpectedAfter("return")
+ },
{
code: "function foo() { {}return+a }",
output: "function foo() { {} return +a }",
parserOptions: { ecmaVersion: 6 },
errors: unexpectedBeforeAndAfter("set")
},
+ {
+ code: "class A { a;set#b(x) {} }",
+ output: "class A { a;set #b(x) {} }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedAfter("set")
+ },
+ {
+ code: "class A { a; set #b(x) {} }",
+ output: "class A { a; set#b(x) {} }",
+ options: [NEITHER],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: unexpectedAfter("set")
+ },
//----------------------------------------------------------------------
// static
parserOptions: { ecmaVersion: 6 },
errors: unexpectedBeforeAndAfter("static")
},
+ {
+ code: "class A { a;static[b]; }",
+ output: "class A { a;static [b]; }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedAfter("static")
+ },
+ {
+ code: "class A { a; static [b]; }",
+ output: "class A { a; static[b]; }",
+ options: [NEITHER],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: unexpectedAfter("static")
+ },
+ {
+ code: "class A { a;static#b; }",
+ output: "class A { a;static #b; }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedAfter("static")
+ },
+ {
+ code: "class A { a; static #b; }",
+ output: "class A { a; static#b; }",
+ options: [NEITHER],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: unexpectedAfter("static")
+ },
+ {
+ code: "class A { a() {}static{} }",
+ output: "class A { a() {} static {} }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedBeforeAndAfter("static")
+ },
+ {
+ code: "class A { a() {}static{} }",
+ output: "class A { a() {} static {} }",
+ options: [override("static", BOTH)],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedBeforeAndAfter("static")
+ },
+ {
+ code: "class A { a() {}static {} }",
+ output: "class A { a() {} static {} }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedBefore("static")
+ },
+ {
+ code: "class A { a() {} static{} }",
+ output: "class A { a() {} static {} }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: expectedAfter("static")
+ },
+ {
+ code: "class A { a() {} static {} }",
+ output: "class A { a() {}static{} }",
+ options: [NEITHER],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: unexpectedBeforeAndAfter("static")
+ },
+ {
+ code: "class A { a() {} static {} }",
+ output: "class A { a() {}static{} }",
+ options: [override("static", NEITHER)],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: unexpectedBeforeAndAfter("static")
+ },
+ {
+ code: "class A { a() {} static{} }",
+ output: "class A { a() {}static{} }",
+ options: [NEITHER],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: unexpectedBefore("static")
+ },
+ {
+ code: "class A { a() {}static {} }",
+ output: "class A { a() {}static{} }",
+ options: [NEITHER],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: unexpectedAfter("static")
+ },
//----------------------------------------------------------------------
// super
options: [override("this", NEITHER)],
errors: unexpectedBefore("this")
},
+ {
+ code: "<Thing> this.blah",
+ output: "<Thing>this.blah",
+ options: [override("this", { before: false })],
+ parser: fixtureParser("keyword-spacing", "prefix-cast-operator-space"),
+ errors: unexpectedBefore("this")
+ },
+ {
+ code: "<Thing>this.blah",
+ output: "<Thing> this.blah",
+ parser: fixtureParser("keyword-spacing", "prefix-cast-operator-no-space"),
+ errors: expectedBefore("this")
+ },
//----------------------------------------------------------------------
// throw
//------------------------------------------------------------------------------
const rule = require("../../../lib/rules/lines-around-comment"),
- { RuleTester } = require("../../../lib/rule-tester");
+ { RuleTester } = require("../../../lib/rule-tester"),
+ { unIndent } = require("../../_utils");
//------------------------------------------------------------------------------
// Tests
allowBlockStart: true
}]
},
+ {
+ code: unIndent`
+ class C {
+ static {
+ // line comment
+ }
+
+ static {
+ // line comment
+ foo();
+ }
+ }`,
+ options: [{
+ beforeLineComment: true,
+ allowBlockStart: true
+ }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C {
+ static
+ {
+ // line comment
+ }
+
+ static
+ {
+ // line comment
+ foo();
+ }
+ }`,
+ options: [{
+ beforeLineComment: true,
+ allowBlockStart: true
+ }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ /* block comment */
+ }
+
+ static {
+ /* block
+ comment */
+ }
+
+ static {
+ /* block comment */
+ foo();
+ }
+
+ static {
+ /* block
+ comment */
+ foo();
+ }
+ }`,
+ options: [{
+ beforeBlockComment: true,
+ allowBlockStart: true
+ }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C {
+ static
+ {
+ /* block comment */
+ }
+
+ static
+ {
+ /* block
+ comment */
+ }
+
+ static
+ {
+ /* block comment */
+ foo();
+ }
+
+ static
+ {
+ /* block
+ comment */
+ foo();
+ }
+ }`,
+ options: [{
+ beforeBlockComment: true,
+ allowBlockStart: true
+ }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
// check for block end comments
{
allowBlockEnd: true
}]
},
+ {
+ code: unIndent`
+ class C {
+ static {
+ // line comment
+ }
+
+ static {
+ foo();
+ // line comment
+ }
+ }`,
+ options: [{
+ afterLineComment: true,
+ allowBlockEnd: true
+ }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ /* block comment */
+ }
+
+ static {
+ /* block
+ comment */
+ }
+
+ static {
+ foo();
+ /* block comment */
+ }
+
+ static {
+ foo();
+ /* block
+ comment */
+ }
+ }`,
+ options: [{
+ beforeBlockComment: false, // default is `true`
+ afterBlockComment: true,
+ allowBlockEnd: true
+ }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
// check for object start comments
{
}],
errors: [{ messageId: "after", type: "Line", line: 8 }]
},
+ {
+ code: unIndent`
+ class C {
+ // line comment
+ static{}
+ }`,
+ output: unIndent`
+ class C {
+ // line comment
+
+ static{}
+ }`,
+ options: [{
+ beforeLineComment: true,
+ afterLineComment: true,
+ allowBlockStart: true,
+ allowBlockEnd: true,
+ allowClassStart: true,
+ allowClassEnd: true
+ }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "after", type: "Line", line: 2 }
+ ]
+ },
+ {
+ code: unIndent`
+ class C {
+ /* block
+ comment */
+ static{}
+ }`,
+ output: unIndent`
+ class C {
+ /* block
+ comment */
+
+ static{}
+ }`,
+ options: [{
+ beforeBlockComment: true,
+ afterBlockComment: true,
+ allowBlockStart: true,
+ allowBlockEnd: true,
+ allowClassStart: true,
+ allowClassEnd: true
+ }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "after", type: "Block", line: 2 }
+ ]
+ },
+ {
+ code: unIndent`
+ class C {
+ static
+ // line comment
+ {}
+ }`,
+ output: unIndent`
+ class C {
+ static
+
+ // line comment
+
+ {}
+ }`,
+ options: [{
+ beforeLineComment: true,
+ afterLineComment: true,
+ allowBlockStart: true,
+ allowBlockEnd: true,
+ allowClassStart: true,
+ allowClassEnd: true
+ }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "before", type: "Line", line: 3 },
+ { messageId: "after", type: "Line", line: 3 }
+ ]
+ },
+ {
+ code: unIndent`
+ class C {
+ static
+ /* block
+ comment */
+ {}
+ }`,
+ output: unIndent`
+ class C {
+ static
+
+ /* block
+ comment */
+
+ {}
+ }`,
+ options: [{
+ beforeBlockComment: true,
+ afterBlockComment: true,
+ allowBlockStart: true,
+ allowBlockEnd: true,
+ allowClassStart: true,
+ allowClassEnd: true
+ }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "before", type: "Block", line: 3 },
+ { messageId: "after", type: "Block", line: 3 }
+ ]
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ // line comment
+ foo();
+ }
+ }`,
+ output: unIndent`
+ class C {
+ static {
+ // line comment
+
+ foo();
+ }
+ }`,
+ options: [{
+ beforeLineComment: true,
+ afterLineComment: true,
+ allowBlockStart: true,
+ allowBlockEnd: true
+ }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "after", type: "Line", line: 3 }
+ ]
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ /* block
+ comment */
+ foo();
+ }
+ }`,
+ output: unIndent`
+ class C {
+ static {
+ /* block
+ comment */
+
+ foo();
+ }
+ }`,
+ options: [{
+ beforeBlockComment: true,
+ afterBlockComment: true,
+ allowBlockStart: true,
+ allowBlockEnd: true
+ }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "after", type: "Block", line: 3 }
+ ]
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ foo();
+ // line comment
+ }
+ }`,
+ output: unIndent`
+ class C {
+ static {
+ foo();
+
+ // line comment
+ }
+ }`,
+ options: [{
+ beforeLineComment: true,
+ afterLineComment: true,
+ allowBlockStart: true,
+ allowBlockEnd: true
+ }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "before", type: "Line", line: 4 }
+ ]
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ foo();
+ /* block
+ comment */
+ }
+ }`,
+ output: unIndent`
+ class C {
+ static {
+ foo();
+
+ /* block
+ comment */
+ }
+ }`,
+ options: [{
+ beforeBlockComment: true,
+ afterBlockComment: true,
+ allowBlockStart: true,
+ allowBlockEnd: true
+ }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "before", type: "Block", line: 4 }
+ ]
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ foo();
+ // line comment
+ bar();
+ }
+ }`,
+ output: unIndent`
+ class C {
+ static {
+ foo();
+
+ // line comment
+
+ bar();
+ }
+ }`,
+ options: [{
+ beforeLineComment: true,
+ afterLineComment: true,
+ allowBlockStart: true,
+ allowBlockEnd: true
+ }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "before", type: "Line", line: 4 },
+ { messageId: "after", type: "Line", line: 4 }
+ ]
+ },
+ {
+ code: unIndent`
+ class C {
+ static {
+ foo();
+ /* block
+ comment */
+ bar();
+ }
+ }`,
+ output: unIndent`
+ class C {
+ static {
+ foo();
+
+ /* block
+ comment */
+
+ bar();
+ }
+ }`,
+ options: [{
+ beforeBlockComment: true,
+ afterBlockComment: true,
+ allowBlockStart: true,
+ allowBlockEnd: true
+ }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "before", type: "Block", line: 4 },
+ { messageId: "after", type: "Block", line: 4 }
+ ]
+ },
+ {
+ code: unIndent`
+ class C {
+ static{}
+ // line comment
+ }`,
+ output: unIndent`
+ class C {
+ static{}
+
+ // line comment
+ }`,
+ options: [{
+ beforeLineComment: true,
+ afterLineComment: true,
+ allowBlockStart: true,
+ allowBlockEnd: true,
+ allowClassStart: true,
+ allowClassEnd: true
+ }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "before", type: "Line", line: 3 }
+ ]
+ },
+ {
+ code: unIndent`
+ class C {
+ static{}
+ /* block
+ comment */
+ }`,
+ output: unIndent`
+ class C {
+ static{}
+
+ /* block
+ comment */
+ }`,
+ options: [{
+ beforeBlockComment: true,
+ afterBlockComment: true,
+ allowBlockStart: true,
+ allowBlockEnd: true,
+ allowClassStart: true,
+ allowClassEnd: true
+ }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "before", type: "Block", line: 3 }
+ ]
+ },
// object start comments
{
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
+
const alwaysError = { messageId: "always" };
const neverError = { messageId: "never" };
// Tests
//------------------------------------------------------------------------------
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } });
ruleTester.run("lines-between-class-members", rule, {
valid: [
"class foo{ bar(){}\n\n;;baz(){}}",
"class foo{ bar(){};\n\nbaz(){}}",
+ "class C {\naaa;\n\n#bbb;\n\nccc(){}\n\n#ddd(){}\n}",
+
{ code: "class foo{ bar(){}\nbaz(){}}", options: ["never"] },
{ code: "class foo{ bar(){}\n/*comments*/baz(){}}", options: ["never"] },
{ code: "class foo{ bar(){}\n//comments\nbaz(){}}", options: ["never"] },
{ code: "class foo{ bar(){}\n\n//comments\nbaz(){}}", options: ["always"] },
{ code: "class foo{ bar(){}\nbaz(){}}", options: ["always", { exceptAfterSingleLine: true }] },
- { code: "class foo{ bar(){\n}\n\nbaz(){}}", options: ["always", { exceptAfterSingleLine: true }] }
+ { code: "class foo{ bar(){\n}\n\nbaz(){}}", options: ["always", { exceptAfterSingleLine: true }] },
+ { code: "class foo{\naaa;\n#bbb;\nccc(){\n}\n\n#ddd(){\n}\n}", options: ["always", { exceptAfterSingleLine: true }] },
+
+ // semicolon-less style (semicolons are at the beginning of lines)
+ { code: "class C { foo\n\n;bar }", options: ["always"] },
+ { code: "class C { foo\n;bar }", options: ["always", { exceptAfterSingleLine: true }] },
+ { code: "class C { foo\n;bar }", options: ["never"] }
],
invalid: [
{
output: "class A {\nfoo() {}\n\n/* comment */;\n;\nbar() {}\n}",
options: ["always"],
errors: [alwaysError]
+ }, {
+ code: "class C {\nfield1\nfield2\n}",
+ output: "class C {\nfield1\n\nfield2\n}",
+ options: ["always"],
+ errors: [alwaysError]
+ }, {
+ code: "class C {\n#field1\n#field2\n}",
+ output: "class C {\n#field1\n\n#field2\n}",
+ options: ["always"],
+ errors: [alwaysError]
+ }, {
+ code: "class C {\nfield1\n\nfield2\n}",
+ output: "class C {\nfield1\nfield2\n}",
+ options: ["never"],
+ errors: [neverError]
+ }, {
+ code: "class C {\nfield1 = () => {\n}\nfield2\nfield3\n}",
+ output: "class C {\nfield1 = () => {\n}\n\nfield2\nfield3\n}",
+ options: ["always", { exceptAfterSingleLine: true }],
+ errors: [alwaysError]
+ },
+ {
+ code: "class C { foo;bar }",
+ output: "class C { foo;\nbar }",
+ options: ["always"],
+ errors: [alwaysError]
+ },
+ {
+ code: "class C { foo;\nbar; }",
+ output: "class C { foo;\n\nbar; }",
+ options: ["always"],
+ errors: [alwaysError]
+ },
+ {
+ code: "class C { foo;\n;bar }",
+ output: "class C { foo;\n\n;bar }",
+ options: ["always"],
+ errors: [alwaysError]
+ },
+
+ // semicolon-less style (semicolons are at the beginning of lines)
+ {
+ code: "class C { foo\n;bar }",
+ output: "class C { foo\n\n;bar }",
+ options: ["always"],
+ errors: [alwaysError]
+ },
+ {
+ code: "class C { foo\n\n;bar }",
+ output: "class C { foo\n;bar }",
+ options: ["never"],
+ errors: [neverError]
+ },
+ {
+ code: "class C { foo\n;;bar }",
+ output: "class C { foo\n\n;;bar }",
+ options: ["always"],
+ errors: [alwaysError]
}
]
});
{
code: "class Foo {}\nclass Bar {}",
options: [2]
+ },
+ {
+ code: "class Foo {}",
+ options: [{ max: 1 }]
+ },
+ {
+ code: "class Foo {}\nclass Bar {}",
+ options: [{ max: 2 }]
+ },
+ {
+ code: `
+ class Foo {}
+ const myExpression = class {}
+ `,
+ options: [{ ignoreExpressions: true, max: 1 }]
+ },
+ {
+ code: `
+ class Foo {}
+ class Bar {}
+ const myExpression = class {}
+ `,
+ options: [{ ignoreExpressions: true, max: 2 }]
}
],
code: "class Foo {}\nclass Bar {}",
errors: [{ messageId: "maximumExceeded", type: "Program" }]
},
+ {
+ code: "class Foo {}\nconst myExpression = class {}",
+ errors: [{ messageId: "maximumExceeded", type: "Program" }]
+ },
{
code: "var x = class {};\nvar y = class {};",
errors: [{ messageId: "maximumExceeded", type: "Program" }]
code: "class Foo {} class Bar {} class Baz {}",
options: [2],
errors: [{ messageId: "maximumExceeded", type: "Program" }]
+ },
+ {
+ code: `
+ class Foo {}
+ class Bar {}
+ const myExpression = class {}
+ `,
+ options: [{ ignoreExpressions: true, max: 1 }],
+ errors: [{ messageId: "maximumExceeded", type: "Program" }]
+ },
+ {
+ code: `
+ class Foo {}
+ class Bar {}
+ class Baz {}
+ const myExpression = class {}
+ `,
+ options: [{ ignoreExpressions: true, max: 2 }],
+ errors: [{ messageId: "maximumExceeded", type: "Program" }]
}
]
});
"function foo() { if (true) { if (false) { if (true) { } } } }",
// object property options
- { code: "function foo() { if (true) { if (false) { if (true) { } } } }", options: [{ max: 3 }] }
+ { code: "function foo() { if (true) { if (false) { if (true) { } } } }", options: [{ max: 3 }] },
+
+ { code: "class C { static { if (1) { if (2) {} } } }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { if (1) { if (2) {} } if (1) { if (2) {} } } }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { if (1) { if (2) {} } } static { if (1) { if (2) {} } } }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "if (1) { class C { static { if (1) { if (2) {} } } } }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "function foo() { if (1) { class C { static { if (1) { if (2) {} } } } } }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ {
+ code: "function foo() { if (1) { if (2) { class C { static { if (1) { if (2) {} } if (1) { if (2) {} } } } } } if (1) { if (2) {} } }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 }
+ }
],
invalid: [
{ code: "function foo() { if (true) { if (false) { if (true) { } } } }", options: [2], errors: [{ messageId: "tooDeeply", data: { depth: 3, maxDepth: 2 }, type: "IfStatement" }] },
{ code: "function foo() { if (true) { if (false) { if (true) { } } } }", options: [{ max: 2 }], errors: [{ messageId: "tooDeeply", data: { depth: 3, maxDepth: 2 }, type: "IfStatement" }] },
{ code: "function foo() { if (a) { if (b) { if (c) { if (d) { if (e) {} } } } } }", options: [{}], errors: [{ messageId: "tooDeeply", data: { depth: 5, maxDepth: 4 } }] },
- { code: "function foo() { if (true) {} }", options: [{ max: 0 }], errors: [{ messageId: "tooDeeply", data: { depth: 1, maxDepth: 0 } }] }
+ { code: "function foo() { if (true) {} }", options: [{ max: 0 }], errors: [{ messageId: "tooDeeply", data: { depth: 1, maxDepth: 0 } }] },
+
+ {
+ code: "class C { static { if (1) { if (2) { if (3) {} } } } }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "tooDeeply",
+ data: { depth: 3, maxDepth: 2 },
+ line: 1,
+ column: 38
+ }]
+ },
+ {
+ code: "if (1) { class C { static { if (1) { if (2) { if (3) {} } } } } }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "tooDeeply",
+ data: { depth: 3, maxDepth: 2 },
+ line: 1,
+ column: 47
+ }]
+ },
+ {
+ code: "function foo() { if (1) { class C { static { if (1) { if (2) { if (3) {} } } } } } }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "tooDeeply",
+ data: { depth: 3, maxDepth: 2 },
+ line: 1,
+ column: 64
+ }]
+ },
+ {
+ code: "function foo() { if (1) { class C { static { if (1) { if (2) {} } } } if (2) { if (3) {} } } }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "tooDeeply",
+ data: { depth: 3, maxDepth: 2 },
+ line: 1,
+ column: 80
+ }]
+ }
]
});
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
+
const rule = require("../../../lib/rules/max-lines-per-function");
const { RuleTester } = require("../../../lib/rule-tester");
const rule = require("../../../lib/rules/max-nested-callbacks"),
{ RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
const OPENING = "foo(function() {",
CLOSING = "});";
{ code: "var foo = { thing() { var bar = 1; var baz = 2; } }", options: [2], parserOptions: { ecmaVersion: 6 } },
{ code: "var foo = { ['thing']() { var bar = 1; var baz = 2; } }", options: [2], parserOptions: { ecmaVersion: 6 } },
{ code: "var foo = { thing: () => { var bar = 1; var baz = 2; } }", options: [2], parserOptions: { ecmaVersion: 6 } },
- { code: "var foo = { thing: function() { var bar = 1; var baz = 2; } }", options: [{ max: 2 }] }
+ { code: "var foo = { thing: function() { var bar = 1; var baz = 2; } }", options: [{ max: 2 }] },
+
+ // this rule does not apply to class static blocks, and statements in them should not count as statements in the enclosing function
+ { code: "class C { static { one; two; three; { four; five; six; } } }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "function foo() { class C { static { one; two; three; { four; five; six; } } } }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { one; two; three; function foo() { 1; 2; } four; five; six; } }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { { one; two; three; function foo() { 1; 2; } four; five; six; } } }", options: [2], parserOptions: { ecmaVersion: 2022 } },
+ {
+ code: "function top_level() { 1; /* 2 */ class C { static { one; two; three; { four; five; six; } } } 3;}",
+ options: [2, { ignoreTopLevelFunctions: true }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "function top_level() { 1; 2; } class C { static { one; two; three; { four; five; six; } } }",
+ options: [1, { ignoreTopLevelFunctions: true }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { one; two; three; { four; five; six; } } } function top_level() { 1; 2; } ",
+ options: [1, { ignoreTopLevelFunctions: true }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "function foo() { let one; let two = class { static { let three; let four; let five; if (six) { let seven; let eight; let nine; } } }; }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 }
+ }
],
invalid: [
{
code: "var foo = { thing: () => { var bar = 1; var baz = 2; var baz2; } }",
options: [2],
parserOptions: { ecmaVersion: 6 },
- errors: [{ messageId: "exceed", data: { name: "Arrow function 'thing'", count: "3", max: 2 } }]
+ errors: [{ messageId: "exceed", data: { name: "Method 'thing'", count: "3", max: 2 } }]
},
{
code: "var foo = { thing: function() { var bar = 1; var baz = 2; var baz2; } }",
code: "function foo() { 1; }",
options: [{ max: 0 }],
errors: [{ messageId: "exceed", data: { name: "Function 'foo'", count: 1, max: 0 } }]
+ },
+ {
+ code: "function foo() { foo_1; /* foo_ 2 */ class C { static { one; two; three; four; { five; six; seven; eight; } } } foo_3 }",
+ options: [2],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "exceed", data: { name: "Function 'foo'", count: 3, max: 2 } }]
+ },
+ {
+ code: "class C { static { one; two; three; four; function not_top_level() { 1; 2; 3; } five; six; seven; eight; } }",
+ options: [2, { ignoreTopLevelFunctions: true }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "exceed", data: { name: "Function 'not_top_level'", count: 3, max: 2 } }]
+ },
+ {
+ code: "class C { static { { one; two; three; four; function not_top_level() { 1; 2; 3; } five; six; seven; eight; } } }",
+ options: [2, { ignoreTopLevelFunctions: true }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "exceed", data: { name: "Function 'not_top_level'", count: 3, max: 2 } }]
+ },
+ {
+ code: "class C { static { { one; two; three; four; } function not_top_level() { 1; 2; 3; } { five; six; seven; eight; } } }",
+ options: [2, { ignoreTopLevelFunctions: true }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "exceed", data: { name: "Function 'not_top_level'", count: 3, max: 2 } }]
}
]
});
const rule = require("../../../lib/rules/multiline-comment-style");
const { RuleTester } = require("../../../lib/rule-tester");
-
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
+
const error = { messageId: "missing", type: "NewExpression" };
const neverError = { messageId: "unnecessary", type: "NewExpression" };
const rule = require("../../../lib/rules/newline-per-chained-call"),
{ RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester();
ruleTester.run("newline-per-chained-call", rule, {
"use strict";
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
const rule = require("../../../lib/rules/no-await-in-loop"),
{ RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const error = { messageId: "unexpectedAwait", type: "AwaitExpression" };
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } });
const rule = require("../../../lib/rules/no-buffer-constructor");
const { RuleTester } = require("../../../lib/rule-tester");
-
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
const rule = require("../../../lib/rules/no-compare-neg-zero");
-
const { RuleTester } = require("../../../lib/rule-tester");
-
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } });
ruleTester.run("no-dupe-class-members", rule, {
valid: [
"class A { [-1]() {} ['-1']() {} }",
// not supported by this rule
- "class A { [foo]() {} [foo]() {} }"
+ "class A { [foo]() {} [foo]() {} }",
+
+ // private and public
+ "class A { foo; static foo; }",
+ "class A { foo; #foo; }",
+ "class A { '#foo'; #foo; }"
],
invalid: [
{
errors: [
{ type: "MethodDefinition", line: 1, column: 29, messageId: "unexpected", data: { name: "foo" } }
]
+ },
+ {
+ code: "class A { foo; foo; }",
+ errors: [
+ { type: "PropertyDefinition", line: 1, column: 16, messageId: "unexpected", data: { name: "foo" } }
+ ]
}
+
+ /*
+ * This is syntax error
+ * { code: "class A { #foo; #foo; }" }
+ */
]
});
"var foo = /\\s*:\\s*/gim;",
{ code: "var foo = /[\\]]/uy;", parserOptions: { ecmaVersion: 6 } },
{ code: "var foo = /[\\]]/s;", parserOptions: { ecmaVersion: 2018 } },
+ { code: "var foo = /[\\]]/d;", parserOptions: { ecmaVersion: 2022 } },
"var foo = /\\[]/"
],
invalid: [
{ code: "if (/^abc[]/.test(foo)) {}", errors: [{ messageId: "unexpected", type: "Literal" }] },
{ code: "var foo = /[]]/;", errors: [{ messageId: "unexpected", type: "Literal" }] },
{ code: "var foo = /\\[[]/;", errors: [{ messageId: "unexpected", type: "Literal" }] },
- { code: "var foo = /\\[\\[\\]a-z[]/;", errors: [{ messageId: "unexpected", type: "Literal" }] }
+ { code: "var foo = /\\[\\[\\]a-z[]/;", errors: [{ messageId: "unexpected", type: "Literal" }] },
+ { code: "var foo = /[]]/d;", parserOptions: { ecmaVersion: 2022 }, errors: [{ messageId: "unexpected", type: "Literal" }] }
]
});
"var obj = {}; obj.foo = function() { this.eval('foo'); }",
{ code: "class A { foo() { this.eval(); } }", parserOptions: { ecmaVersion: 6 } },
{ code: "class A { static foo() { this.eval(); } }", parserOptions: { ecmaVersion: 6 } },
+ { code: "class A { field = this.eval(); }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { field = () => this.eval(); }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { static { this.eval(); } }", parserOptions: { ecmaVersion: 2022 } },
// Allows indirect eval
{ code: "(0, eval)('foo')", options: [{ allowIndirect: true }] },
parserOptions: { ecmaVersion: 2020 },
globals: { window: "readonly" },
errors: [{ messageId: "unexpected" }]
+ },
+
+ // Class fields
+ {
+ code: "class C { [this.eval('foo')] }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "unexpected" }]
+ },
+
+ {
+ code: "class A { static {} [this.eval()]; }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "unexpected" }]
}
]
});
{ RuleTester } = require("../../../lib/rule-tester");
//------------------------------------------------------------------------------
-// Helpers
+// Tests
//------------------------------------------------------------------------------
const ruleTester = new RuleTester();
const rule = require("../../../lib/rules/no-extra-parens"),
{ RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
/**
* Create error message object for failure cases
* @param {string} code source code
return result;
}
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester({
parserOptions: {
- ecmaVersion: 2021,
+ ecmaVersion: 2022,
ecmaFeatures: {
jsx: true
}
"class foo { a(){} [b](){} c(){} [(d,e)](){} }",
"class foo { [(a,b)](){} c(){} [d](){} e(){} }",
"const foo = class { constructor(){} a(){} get b(){} set b(bar){} get c(){} set d(baz){} static e(){} }",
+ "class foo { x; }",
+ "class foo { static x; }",
+ "class foo { x = 1; }",
+ "class foo { static x = 1; }",
+ "class foo { #x; }",
+ "class foo { static #x; }",
+ "class foo { static #x = 1; }",
+ "class foo { #x(){} get #y() {} set #y(value) {} static #z(){} static get #q() {} static set #q(value) {} }",
+ "const foo = class { #x(){} get #y() {} set #y(value) {} static #z(){} static get #q() {} static set #q(value) {} }",
+ "class foo { [(x, y)]; }",
+ "class foo { static [(x, y)]; }",
+ "class foo { [(x, y)] = 1; }",
+ "class foo { static [(x, y)] = 1; }",
+ "class foo { x = (y, z); }",
+ "class foo { static x = (y, z); }",
+ "class foo { #x = (y, z); }",
+ "class foo { static #x = (y, z); }",
+ "class foo { [(1, 2)] = (3, 4) }",
+ "const foo = class { [(1, 2)] = (3, 4) }",
// ExpressionStatement restricted productions
"({});",
invalid("class foo { [(a,b)](){} [(c+d)](){} }", "class foo { [(a,b)](){} [c+d](){} }", "BinaryExpression"),
invalid("class foo { [a+(b*c)](){} }", "class foo { [a+b*c](){} }", "BinaryExpression"),
invalid("const foo = class { [(a)](){} }", "const foo = class { [a](){} }", "Identifier"),
+ invalid("class foo { [(x)]; }", "class foo { [x]; }", "Identifier"),
+ invalid("class foo { static [(x)]; }", "class foo { static [x]; }", "Identifier"),
+ invalid("class foo { [(x)] = 1; }", "class foo { [x] = 1; }", "Identifier"),
+ invalid("class foo { static [(x)] = 1; }", "class foo { static [x] = 1; }", "Identifier"),
+ invalid("const foo = class { [(x)]; }", "const foo = class { [x]; }", "Identifier"),
+ invalid("class foo { [(x = y)]; }", "class foo { [x = y]; }", "AssignmentExpression"),
+ invalid("class foo { [(x + y)]; }", "class foo { [x + y]; }", "BinaryExpression"),
+ invalid("class foo { [(x ? y : z)]; }", "class foo { [x ? y : z]; }", "ConditionalExpression"),
+ invalid("class foo { [((x, y))]; }", "class foo { [(x, y)]; }", "SequenceExpression"),
+ invalid("class foo { x = (y); }", "class foo { x = y; }", "Identifier"),
+ invalid("class foo { static x = (y); }", "class foo { static x = y; }", "Identifier"),
+ invalid("class foo { #x = (y); }", "class foo { #x = y; }", "Identifier"),
+ invalid("class foo { static #x = (y); }", "class foo { static #x = y; }", "Identifier"),
+ invalid("const foo = class { x = (y); }", "const foo = class { x = y; }", "Identifier"),
+ invalid("class foo { x = (() => {}); }", "class foo { x = () => {}; }", "ArrowFunctionExpression"),
+ invalid("class foo { x = (y + z); }", "class foo { x = y + z; }", "BinaryExpression"),
+ invalid("class foo { x = (y ? z : q); }", "class foo { x = y ? z : q; }", "ConditionalExpression"),
+ invalid("class foo { x = ((y, z)); }", "class foo { x = (y, z); }", "SequenceExpression"),
+
+ //
invalid(
"var foo = (function*() { if ((yield foo())) { return; } }())",
"var foo = (function*() { if (yield foo()) { return; } }())",
{ code: "class A { a() { this; } }", parserOptions: { ecmaVersion: 6 } },
{ code: "var A = class { a() { this; } };", parserOptions: { ecmaVersion: 6 } },
{ code: "class A { } a;", parserOptions: { ecmaVersion: 6 } },
+ { code: "class A { field; }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { field = 0; }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A { static { foo; } }", parserOptions: { ecmaVersion: 2022 } },
// modules
{ code: "export const x = 42;", parserOptions: { ecmaVersion: 6, sourceType: "module" } },
output: "with(foo){}",
errors: [{ messageId: "unexpected", type: "EmptyStatement" }]
},
+ {
+ code: "class A { static { ; } }",
+ output: "class A { static { } }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "unexpected", type: "EmptyStatement", column: 20 }]
+ },
+ {
+ code: "class A { static { a;; } }",
+ output: "class A { static { a; } }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "unexpected", type: "EmptyStatement", column: 22 }]
+ },
// Class body.
{
output: "class A { a() {} get b() {} }",
parserOptions: { ecmaVersion: 6 },
errors: [{ messageId: "unexpected", type: "Punctuator", column: 17 }]
+ },
+ {
+ code: "class A { field;; }",
+ output: "class A { field; }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "unexpected", type: "Punctuator", column: 17 }]
+ },
+ {
+ code: "class A { static {}; }",
+ output: "class A { static {} }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "unexpected", type: "Punctuator", column: 20 }]
+ },
+ {
+ code: "class A { static { a; }; foo(){} }",
+ output: "class A { static { a; } foo(){} }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "unexpected", type: "Punctuator", column: 24 }]
}
]
});
"switch(foo) { case 0: a(); /* fall through */ case 1: b(); }",
"switch(foo) { case 0: a(); /* fallthrough */ case 1: b(); }",
"switch(foo) { case 0: a(); /* FALLS THROUGH */ case 1: b(); }",
+ "switch(foo) { case 0: { a(); /* falls through */ } case 1: b(); }",
+ "switch(foo) { case 0: { a()\n /* falls through */ } case 1: b(); }",
+ "switch(foo) { case 0: { a(); /* fall through */ } case 1: b(); }",
+ "switch(foo) { case 0: { a(); /* fallthrough */ } case 1: b(); }",
+ "switch(foo) { case 0: { a(); /* FALLS THROUGH */ } case 1: b(); }",
+ "switch(foo) { case 0: { a(); } /* falls through */ case 1: b(); }",
+ "switch(foo) { case 0: { a(); /* falls through */ } /* comment */ case 1: b(); }",
+ "switch(foo) { case 0: { /* falls through */ } case 1: b(); }",
"function foo() { switch(foo) { case 0: a(); return; case 1: b(); }; }",
"switch(foo) { case 0: a(); throw 'foo'; case 1: b(); }",
"while (a) { switch(foo) { case 0: a(); continue; case 1: b(); } }",
code: "switch(foo) { case 0:\n\n default: b() }",
errors: errorsDefault
},
+ {
+ code: "switch(foo) { case 0: {} default: b() }",
+ errors: errorsDefault
+ },
+ {
+ code: "switch(foo) { case 0: a(); { /* falls through */ } default: b() }",
+ errors: errorsDefault
+ },
+ {
+ code: "switch(foo) { case 0: { /* falls through */ } a(); default: b() }",
+ errors: errorsDefault
+ },
+ {
+ code: "switch(foo) { case 0: if (a) { /* falls through */ } default: b() }",
+ errors: errorsDefault
+ },
+ {
+ code: "switch(foo) { case 0: { { /* falls through */ } } default: b() }",
+ errors: errorsDefault
+ },
+ {
+ code: "switch(foo) { case 0: { /* comment */ } default: b() }",
+ errors: errorsDefault
+ },
{
code: "switch(foo) { case 0:\n // comment\n default: b() }",
errors: errorsDefault
column: 1
}
]
+ },
+ {
+ code: "switch(foo) { case 0: { a();\n/* no break */\n/* todo: fix readability */ }\ndefault: b() }",
+ options: [{
+ commentPattern: "no break"
+ }],
+ errors: [
+ {
+ messageId: "default",
+ type: "SwitchCase",
+ line: 4,
+ column: 1
+ }
+ ]
}
]
});
{
code: "module.exports = function foo(){}",
options: ["both"]
+ },
+ {
+ code: "class C { method() { function foo() {} } }",
+ options: ["both"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { method() { var x; } }",
+ options: ["both"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { function foo() {} } }",
+ options: ["both"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { var x; } }",
+ options: ["both"],
+ parserOptions: { ecmaVersion: 2022 }
}
-
],
// Examples of code that should trigger the rule
},
type: "VariableDeclaration"
}]
+ }, {
+ code: "class C { method() { if(test) { var foo; } } }",
+ options: ["both"],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [{
+ messageId: "moveDeclToRoot",
+ data: {
+ type: "variable",
+ body: "function body"
+ },
+ type: "VariableDeclaration"
+ }]
+ }, {
+ code: "class C { static { if (test) { function foo() {} } } }",
+ options: ["both"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "moveDeclToRoot",
+ data: {
+ type: "function",
+ body: "class static block body"
+ },
+ type: "FunctionDeclaration"
+ }]
+ }, {
+ code: "class C { static { if (test) { var foo; } } }",
+ options: ["both"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "moveDeclToRoot",
+ data: {
+ type: "variable",
+ body: "class static block body"
+ },
+ type: "VariableDeclaration"
+ }]
+ }, {
+ code: "class C { static { if (test) { if (anotherTest) { var foo; } } } }",
+ options: ["both"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "moveDeclToRoot",
+ data: {
+ type: "variable",
+ body: "class static block body"
+ },
+ type: "VariableDeclaration"
+ }]
}
-
]
});
const rule = require("../../../lib/rules/no-invalid-regexp"),
{ RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester();
ruleTester.run("no-invalid-regexp", rule, {
"new RegExp('(?<𝒜>.)', 'g');",
"new RegExp('\\\\p{Script=Nandinagari}', 'u');",
+ // ES2022
+ "new RegExp('a+(?<Z>z)?', 'd')",
+
// allowConstructorFlags
{
code: "new RegExp('.', 'g')",
return [].concat(...patternsList);
}
-
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
valid: [NORMAL],
invalid: [USE_STRICT, IMPLIED_STRICT, MODULES]
},
+
+ // Logical assignments
{
code: "obj.method &&= function () { console.log(this); z(x => console.log(x, this)); }",
parserOptions: { ecmaVersion: 2021 },
parserOptions: { ecmaVersion: 2021 },
valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
invalid: []
+ },
+
+ // Class fields.
+ {
+ code: "class C { field = console.log(this); }",
+ parserOptions: { ecmaVersion: 2022 },
+ valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+ invalid: []
+ },
+ {
+ code: "class C { field = z(x => console.log(x, this)); }",
+ parserOptions: { ecmaVersion: 2022 },
+ valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+ invalid: []
+ },
+ {
+ code: "class C { field = function () { console.log(this); z(x => console.log(x, this)); }; }",
+ parserOptions: { ecmaVersion: 2022 },
+ valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+ invalid: []
+ },
+ {
+ code: "class C { #field = function () { console.log(this); z(x => console.log(x, this)); }; }",
+ parserOptions: { ecmaVersion: 2022 },
+ valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+ invalid: []
+ },
+ {
+ code: "class C { [this.foo]; }",
+ parserOptions: { ecmaVersion: 2022 },
+ valid: [NORMAL], // the global this in non-strict mode is OK.
+ invalid: [USE_STRICT, IMPLIED_STRICT, MODULES],
+ errors: [{ messageId: "unexpectedThis", type: "ThisExpression" }]
+ },
+
+ // Class static blocks
+ {
+ code: "class C { static { this.x; } }",
+ parserOptions: { ecmaVersion: 2022 },
+ valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+ invalid: []
+ },
+ {
+ code: "class C { static { () => { this.x; } } }",
+ parserOptions: { ecmaVersion: 2022 },
+ valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+ invalid: []
+ },
+ {
+ code: "class C { static { class D { [this.x]; } } }",
+ parserOptions: { ecmaVersion: 2022 },
+ valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+ invalid: []
+ },
+ {
+ code: "class C { static { function foo() { this.x; } } }",
+ parserOptions: { ecmaVersion: 2022 },
+ valid: [],
+ invalid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+ errors: [{ messageId: "unexpectedThis", type: "ThisExpression" }]
+ },
+ {
+ code: "class C { static { (function() { this.x; }); } }",
+ parserOptions: { ecmaVersion: 2022 },
+ valid: [],
+ invalid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+ errors: [{ messageId: "unexpectedThis", type: "ThisExpression" }]
+ },
+ {
+ code: "class C { static { (function() { this.x; })(); } }",
+ parserOptions: { ecmaVersion: 2022 },
+ valid: [],
+ invalid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+ errors: [{ messageId: "unexpectedThis", type: "ThisExpression" }]
+ },
+ {
+ code: "class C { static {} [this.x]; }",
+ parserOptions: { ecmaVersion: 2022 },
+ valid: [NORMAL],
+ invalid: [USE_STRICT, IMPLIED_STRICT, MODULES],
+ errors: [{ messageId: "unexpectedThis", type: "ThisExpression" }]
}
];
{ RuleTester } = require("../../../lib/rule-tester");
//------------------------------------------------------------------------------
-// Helpers
+// Tests
//------------------------------------------------------------------------------
const ruleTester = new RuleTester();
}
}
`,
- { code: "function foo() { { const x = 4 } const x = 3 }", parserOptions: { ecmaVersion: 6 } }
+ { code: "function foo() { { const x = 4 } const x = 3 }", parserOptions: { ecmaVersion: 6 } },
+
+ { code: "class C { static {} }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { foo; } }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { if (foo) { block; } } }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { lbl: { block; } } }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { { let block; } something; } }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { something; { const block = 1; } } }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { { function block(){} } something; } }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { something; { class block {} } } }", parserOptions: { ecmaVersion: 2022 } }
],
invalid: [
{
type: "BlockStatement",
line: 3
}]
+ },
+ {
+ code: `
+ class C {
+ static {
+ if (foo) {
+ {
+ let block;
+ }
+ }
+ }
+ }
+ `,
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "redundantNestedBlock",
+ type: "BlockStatement",
+ line: 5
+ }]
+ },
+ {
+ code: `
+ class C {
+ static {
+ if (foo) {
+ {
+ block;
+ }
+ something;
+ }
+ }
+ }
+ `,
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "redundantNestedBlock",
+ type: "BlockStatement",
+ line: 5
+ }]
+ },
+ {
+ code: `
+ class C {
+ static {
+ {
+ block;
+ }
+ }
+ }
+ `,
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "redundantNestedBlock",
+ type: "BlockStatement",
+ line: 4
+ }]
+ },
+ {
+ code: `
+ class C {
+ static {
+ {
+ let block;
+ }
+ }
+ }
+ `,
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "redundantNestedBlock",
+ type: "BlockStatement",
+ line: 4
+ }]
+ },
+ {
+ code: `
+ class C {
+ static {
+ {
+ const block = 1;
+ }
+ }
+ }
+ `,
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "redundantNestedBlock",
+ type: "BlockStatement",
+ line: 4
+ }]
+ },
+ {
+ code: `
+ class C {
+ static {
+ {
+ function block() {}
+ }
+ }
+ }
+ `,
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "redundantNestedBlock",
+ type: "BlockStatement",
+ line: 4
+ }]
+ },
+ {
+ code: `
+ class C {
+ static {
+ {
+ class block {}
+ }
+ }
+ }
+ `,
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "redundantNestedBlock",
+ type: "BlockStatement",
+ line: 4
+ }]
+ },
+ {
+ code: `
+ class C {
+ static {
+ {
+ var block;
+ }
+ something;
+ }
+ }
+ `,
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "redundantNestedBlock",
+ type: "BlockStatement",
+ line: 4
+ }]
+ },
+ {
+ code: `
+ class C {
+ static {
+ something;
+ {
+ var block;
+ }
+ }
+ }
+ `,
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "redundantNestedBlock",
+ type: "BlockStatement",
+ line: 5
+ }]
+ },
+ {
+ code: `
+ class C {
+ static {
+ {
+ block;
+ }
+ something;
+ }
+ }
+ `,
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "redundantNestedBlock",
+ type: "BlockStatement",
+ line: 4
+ }]
+ },
+ {
+ code: `
+ class C {
+ static {
+ something;
+ {
+ block;
+ }
+ }
+ }
+ `,
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "redundantNestedBlock",
+ type: "BlockStatement",
+ line: 5
+ }]
}
]
});
{ RuleTester } = require("../../../lib/rule-tester");
//------------------------------------------------------------------------------
-// Helpers
+// Tests
//------------------------------------------------------------------------------
const ruleTester = new RuleTester();
{ RuleTester } = require("../../../lib/rule-tester");
//------------------------------------------------------------------------------
-// Helpers
+// Tests
//------------------------------------------------------------------------------
const ruleTester = new RuleTester();
{ RuleTester } = require("../../../lib/rule-tester");
//------------------------------------------------------------------------------
-// Helpers
+// Tests
//------------------------------------------------------------------------------
const ruleTester = new RuleTester({
{ RuleTester } = require("../../../lib/rule-tester");
//------------------------------------------------------------------------------
-// Fixtures
+// Helpers
//------------------------------------------------------------------------------
/**
};
}
-
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
{ code: "export let a, b;", parserOptions: { ecmaVersion: 6, sourceType: "module" } },
{ code: "export let a,\n b = 0;", parserOptions: { ecmaVersion: 6, sourceType: "module" } },
{ code: "const x = {};const y = {};x.one = y.one = 1;", options: [{ ignoreNonDeclaration: true }], parserOptions: { ecmaVersion: 6 } },
- { code: "let a, b;a = b = 1", options: [{ ignoreNonDeclaration: true }], parserOptions: { ecmaVersion: 6 } }
+ { code: "let a, b;a = b = 1", options: [{ ignoreNonDeclaration: true }], parserOptions: { ecmaVersion: 6 } },
+ {
+ code: "class C { [foo = 0] = 0 }",
+ parserOptions: { ecmaVersion: 2022 }
+ }
],
invalid: [
errors: [
errorAt(1, 11, "AssignmentExpression")
]
+ },
+ {
+ code: "class C { field = foo = 0 }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ errorAt(1, 19, "AssignmentExpression")
+ ]
+ },
+ {
+ code: "class C { field = foo = 0 }",
+ options: [{ ignoreNonDeclaration: true }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ errorAt(1, 19, "AssignmentExpression")
+ ]
}
]
});
{ RuleTester } = require("../../../lib/rule-tester");
//------------------------------------------------------------------------------
-// Tests
+// Helpers
//------------------------------------------------------------------------------
-const ruleTester = new RuleTester();
-
/**
* Creates the expected error message object for the specified number of lines
* @param {lines} lines The number of lines expected.
};
}
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester();
ruleTester.run("no-multiple-empty-lines", rule, {
"var fn = function () { function Function() {}; Function() }",
"var x = function Function() { Function(); }",
"call(Function)",
- "new Class(Function)"
+ "new Class(Function)",
+ "foo[Function]()",
+ "foo(Function.bind)",
+ "Function.toString()",
+ "Function[call]()"
],
invalid: [
{
type: "CallExpression"
}]
},
+ {
+ code: "var a = Function.call(null, \"b\", \"c\", \"return b+c\");",
+ errors: [{
+ messageId: "noFunctionConstructor",
+ type: "CallExpression"
+ }]
+ },
+ {
+ code: "var a = Function.apply(null, [\"b\", \"c\", \"return b+c\"]);",
+ errors: [{
+ messageId: "noFunctionConstructor",
+ type: "CallExpression"
+ }]
+ },
+ {
+ code: "var a = Function.bind(null, \"b\", \"c\", \"return b+c\")();",
+ errors: [{
+ messageId: "noFunctionConstructor",
+ type: "CallExpression"
+ }]
+ },
+ {
+ code: "var a = Function.bind(null, \"b\", \"c\", \"return b+c\");",
+ errors: [{
+ messageId: "noFunctionConstructor",
+ type: "CallExpression"
+ }]
+ },
+ {
+ code: "var a = Function[\"call\"](null, \"b\", \"c\", \"return b+c\");",
+ errors: [{
+ messageId: "noFunctionConstructor",
+ type: "CallExpression"
+ }]
+ },
+ {
+ code: "var a = (Function?.call)(null, \"b\", \"c\", \"return b+c\");",
+ parserOptions: { ecmaVersion: 2021 },
+ errors: [{
+ messageId: "noFunctionConstructor",
+ type: "CallExpression"
+ }]
+ },
{
code: "const fn = () => { class Function {} }; new Function('', '')",
parserOptions: {
"var a = test[__proto__];",
"var __proto__ = null;",
{ code: "foo[`__proto`] = null;", parserOptions: { ecmaVersion: 6 } },
- { code: "foo[`__proto__\n`] = null;", parserOptions: { ecmaVersion: 6 } }
+ { code: "foo[`__proto__\n`] = null;", parserOptions: { ecmaVersion: 6 } },
+ { code: "class C { #__proto__; foo() { this.#__proto__; } }", parserOptions: { ecmaVersion: 2022 } }
],
invalid: [
{ code: "var a = test.__proto__;", errors: [{ messageId: "unexpectedProto", type: "MemberExpression" }] },
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester();
ruleTester.run("no-prototype-builtins", rule, {
{ code: "foo?.['propertyIsEnumerabl']('bar')", parserOptions: { ecmaVersion: 2020 } },
"foo[1]('bar')",
"foo[null]('bar')",
+ { code: "class C { #hasOwnProperty; foo() { obj.#hasOwnProperty('bar'); } }", parserOptions: { ecmaVersion: 2022 } },
// out of scope for this rule
"foo['hasOwn' + 'Property']('bar')",
ecmaVersion: 6
}
},
+ {
+ code: "var a; class C { static { var a; } }",
+ parserOptions: {
+ ecmaVersion: 2022
+ }
+ },
+ {
+ code: "class C { static { var a; } } var a; ",
+ parserOptions: {
+ ecmaVersion: 2022
+ }
+ },
+ {
+ code: "function a(){} class C { static { var a; } }",
+ parserOptions: {
+ ecmaVersion: 2022
+ }
+ },
+ {
+ code: "var a; class C { static { function a(){} } }",
+ parserOptions: {
+ ecmaVersion: 2022
+ }
+ },
+ {
+ code: "class C { static { var a; } static { var a; } }",
+ parserOptions: {
+ ecmaVersion: 2022
+ }
+ },
+ {
+ code: "class C { static { function a(){} } static { function a(){} } }",
+ parserOptions: {
+ ecmaVersion: 2022
+ }
+ },
+ {
+ code: "class C { static { var a; { function a(){} } } }",
+ parserOptions: {
+ ecmaVersion: 2022
+ }
+ },
+ {
+ code: "class C { static { function a(){}; { function a(){} } } }",
+ parserOptions: {
+ ecmaVersion: 2022
+ }
+ },
+ {
+ code: "class C { static { var a; { let a; } } }",
+ parserOptions: {
+ ecmaVersion: 2022
+ }
+ },
+ {
+ code: "class C { static { let a; { let a; } } }",
+ parserOptions: {
+ ecmaVersion: 2022
+ }
+ },
+ {
+ code: "class C { static { { let a; } { let a; } } }",
+ parserOptions: {
+ ecmaVersion: 2022
+ }
+ },
{ code: "var Object = 0;", options: [{ builtinGlobals: false }] },
{ code: "var Object = 0;", options: [{ builtinGlobals: true }], parserOptions: { ecmaVersion: 6, sourceType: "module" } },
{ code: "var Object = 0;", options: [{ builtinGlobals: true }], parserOptions: { ecmaFeatures: { globalReturn: true } } },
{ code: "var a = 3; var a = 10; var a = 15;", errors: [{ message: "'a' is already defined.", type: "Identifier" }, { message: "'a' is already defined.", type: "Identifier" }] },
{ code: "var a; var a;", parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ message: "'a' is already defined.", type: "Identifier" }] },
{ code: "export var a; var a;", parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ message: "'a' is already defined.", type: "Identifier" }] },
+
+ // `var` redeclaration in class static blocks. Redeclaration of functions is not allowed in class static blocks.
+ {
+ code: "class C { static { var a; var a; } }",
+ parserOptions: {
+ ecmaVersion: 2022
+ },
+ errors: [{ message: "'a' is already defined.", type: "Identifier" }]
+ },
+ {
+ code: "class C { static { var a; { var a; } } }",
+ parserOptions: {
+ ecmaVersion: 2022
+ },
+ errors: [{ message: "'a' is already defined.", type: "Identifier" }]
+ },
+ {
+ code: "class C { static { { var a; } var a; } }",
+ parserOptions: {
+ ecmaVersion: 2022
+ },
+ errors: [{ message: "'a' is already defined.", type: "Identifier" }]
+ },
+ {
+ code: "class C { static { { var a; } { var a; } } }",
+ parserOptions: {
+ ecmaVersion: 2022
+ },
+ errors: [{ message: "'a' is already defined.", type: "Identifier" }]
+ },
+
{
code: "var Object = 0;",
options: [{ builtinGlobals: true }],
const rule = require("../../../lib/rules/no-regex-spaces"),
{ RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester();
ruleTester.run("no-regex-spaces", rule, {
{ code: "import \"foo/bar\";", options: ["foo"] },
{ code: "import withPaths from \"foo/bar\";", options: [{ paths: ["foo", "bar"] }] },
{ code: "import withPatterns from \"foo/bar\";", options: [{ patterns: ["foo/c*"] }] },
+ { code: "import foo from 'foo';", options: ["../foo"] },
+ { code: "import foo from 'foo';", options: [{ paths: ["../foo"] }] },
+ { code: "import foo from 'foo';", options: [{ patterns: ["../foo"] }] },
+ { code: "import foo from 'foo';", options: ["/foo"] },
+ { code: "import foo from 'foo';", options: [{ paths: ["/foo"] }] },
+ "import relative from '../foo';",
+ { code: "import relative from '../foo';", options: ["../notFoo"] },
+ { code: "import relativeWithPaths from '../foo';", options: [{ paths: ["../notFoo"] }] },
+ { code: "import relativeWithPatterns from '../foo';", options: [{ patterns: ["notFoo"] }] },
+ "import absolute from '/foo';",
+ { code: "import absolute from '/foo';", options: ["/notFoo"] },
+ { code: "import absoluteWithPaths from '/foo';", options: [{ paths: ["/notFoo"] }] },
+ { code: "import absoluteWithPatterns from '/foo';", options: [{ patterns: ["notFoo"] }] },
{
code: "import withPatternsAndPaths from \"foo/bar\";",
options: [{ paths: ["foo"], patterns: ["foo/c*"] }]
column: 15,
endColumn: 29
}]
+ },
+ {
+ code: "import relative from '../foo';",
+ options: ["../foo"],
+ errors: [{
+ message: "'../foo' import is restricted from being used.",
+ type: "ImportDeclaration",
+ line: 1,
+ column: 1,
+ endColumn: 31
+ }]
+ },
+ {
+ code: "import relativeWithPaths from '../foo';",
+ options: [{ paths: ["../foo"] }],
+ errors: [{
+ message: "'../foo' import is restricted from being used.",
+ type: "ImportDeclaration",
+ line: 1,
+ column: 1,
+ endColumn: 40
+ }]
+ },
+ {
+ code: "import relativeWithPatterns from '../foo';",
+ options: [{ patterns: ["../foo"] }],
+ errors: [{
+ message: "'../foo' import is restricted from being used by a pattern.",
+ type: "ImportDeclaration",
+ line: 1,
+ column: 1,
+ endColumn: 43
+ }]
+ },
+ {
+ code: "import absolute from '/foo';",
+ options: ["/foo"],
+ errors: [{
+ message: "'/foo' import is restricted from being used.",
+ type: "ImportDeclaration",
+ line: 1,
+ column: 1,
+ endColumn: 29
+ }]
+ },
+ {
+ code: "import absoluteWithPaths from '/foo';",
+ options: [{ paths: ["/foo"] }],
+ errors: [{
+ message: "'/foo' import is restricted from being used.",
+ type: "ImportDeclaration",
+ line: 1,
+ column: 1,
+ endColumn: 38
+ }]
+ },
+ {
+ code: "import absoluteWithPatterns from '/foo';",
+ options: [{ patterns: ["foo"] }],
+ errors: [{
+ message: "'/foo' import is restricted from being used by a pattern.",
+ type: "ImportDeclaration",
+ line: 1,
+ column: 1,
+ endColumn: 41
+ }]
}
]
});
{ code: "var withPatternsAndPaths = require(\"foo/bar\");", options: [{ paths: ["foo"], patterns: ["foo/c*"] }] },
{ code: "var withGitignores = require(\"foo/bar\");", options: [{ paths: ["foo"], patterns: ["foo/*", "!foo/bar"] }] },
{ code: "require(`fs`)", options: ["crypto"], parserOptions: { ecmaVersion: 6 } },
- { code: "require(`foo${bar}`)", options: ["foo"], parserOptions: { ecmaVersion: 6 } }
+ { code: "require(`foo${bar}`)", options: ["foo"], parserOptions: { ecmaVersion: 6 } },
+ { code: "var foo = require('foo');", options: ["../foo"] },
+ { code: "var foo = require('foo');", options: [{ paths: ["../foo"] }] },
+ { code: "var foo = require('foo');", options: [{ patterns: ["../foo"] }] },
+ { code: "var foo = require('foo');", options: ["/foo"] },
+ { code: "var foo = require('foo');", options: [{ paths: ["/foo"] }] },
+ "var relative = require('../foo');",
+ { code: "var relative = require('../foo');", options: ["../notFoo"] },
+ { code: "var relativeWithPaths = require('../foo');", options: [{ paths: ["../notFoo"] }] },
+ { code: "var relativeWithPatterns = require('../foo');", options: [{ patterns: ["notFoo"] }] },
+ "var absolute = require('/foo');",
+ { code: "var absolute = require('/foo');", options: ["/notFoo"] },
+ { code: "var absoluteWithPaths = require('/foo');", options: [{ paths: ["/notFoo"] }] },
+ { code: "var absoluteWithPatterns = require('/foo');", options: [{ patterns: ["notFoo"] }] }
],
invalid: [{
code: "require(\"fs\")",
options: ["crypto"],
parserOptions: { ecmaVersion: 6 },
errors: [{ messageId: "defaultMessage", data: { name: "crypto" }, type: "CallExpression" }]
+ },
+ {
+ code: "var relative = require('../foo');",
+ options: ["../foo"],
+ errors: [{
+ message: "'../foo' module is restricted from being used.",
+ type: "CallExpression",
+ line: 1,
+ column: 16,
+ endColumn: 33
+ }]
+ },
+ {
+ code: "var relativeWithPaths = require('../foo');",
+ options: [{ paths: ["../foo"] }],
+ errors: [{
+ message: "'../foo' module is restricted from being used.",
+ type: "CallExpression",
+ line: 1,
+ column: 25,
+ endColumn: 42
+ }]
+ },
+ {
+ code: "var relativeWithPatterns = require('../foo');",
+ options: [{ patterns: ["../foo"] }],
+ errors: [{
+ message: "'../foo' module is restricted from being used by a pattern.",
+ type: "CallExpression",
+ line: 1,
+ column: 28,
+ endColumn: 45
+ }]
+ },
+ {
+ code: "var absolute = require('/foo');",
+ options: ["/foo"],
+ errors: [{
+ message: "'/foo' module is restricted from being used.",
+ type: "CallExpression",
+ line: 1,
+ column: 16,
+ endColumn: 31
+ }]
+ },
+ {
+ code: "var absoluteWithPaths = require('/foo');",
+ options: [{ paths: ["/foo"] }],
+ errors: [{
+ message: "'/foo' module is restricted from being used.",
+ type: "CallExpression",
+ line: 1,
+ column: 25,
+ endColumn: 40
+ }]
+ },
+ {
+ code: "var absoluteWithPatterns = require('/foo');",
+ options: [{ patterns: ["foo"] }],
+ errors: [{
+ message: "'/foo' module is restricted from being used by a pattern.",
+ type: "CallExpression",
+ line: 1,
+ column: 28,
+ endColumn: 43
+ }]
}]
});
code: "function qux([, bar] = foo) {}",
options: [{ object: "foo", property: "1" }],
parserOptions: { ecmaVersion: 6 }
+ }, {
+ code: "class C { #foo; foo() { this.#foo; } }",
+ options: [{ property: "#foo" }],
+ parserOptions: { ecmaVersion: 2022 }
}
],
},
type: "ObjectPattern"
}]
+ }, {
+ code: "obj['#foo']",
+ options: [{ property: "#foo" }],
+ errors: [{
+ messageId: "restrictedProperty",
+ data: {
+ objectName: "",
+ propertyName: "#foo",
+ message: ""
+ },
+ type: "MemberExpression"
+ }]
}
]
});
{
code: "this.x = this.x",
options: [{ props: false }]
+ },
+ {
+ code: "class C { #field; foo() { this['#field'] = this.#field; } }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { #field; foo() { this.#field = this['#field']; } }",
+ parserOptions: { ecmaVersion: 2022 }
}
],
invalid: [
code: "a.b = a?.b",
parserOptions: { ecmaVersion: 2020 },
errors: [{ messageId: "selfAssignment", data: { name: "a?.b" } }]
+ },
+
+ // Private members
+ {
+ code: "class C { #field; foo() { this.#field = this.#field; } }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "selfAssignment", data: { name: "this.#field" } }]
+ },
+ {
+ code: "class C { #field; foo() { [this.#field] = [this.#field]; } }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "selfAssignment", data: { name: "this.#field" } }]
}
]
});
"if (x === y) { }",
"if (1 === 2) { }",
"y=x*x",
- "foo.bar.baz === foo.bar.qux"
+ "foo.bar.baz === foo.bar.qux",
+ {
+ code: "class C { #field; foo() { this.#field === this['#field']; } }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { #field; foo() { this['#field'] === this.#field; } }",
+ parserOptions: { ecmaVersion: 2022 }
+ }
],
invalid: [
{ code: "if (x === x) { }", errors: [{ messageId: "comparingToSelf", type: "BinaryExpression" }] },
{ code: "x < x", errors: [{ messageId: "comparingToSelf", type: "BinaryExpression" }] },
{ code: "x >= x", errors: [{ messageId: "comparingToSelf", type: "BinaryExpression" }] },
{ code: "x <= x", errors: [{ messageId: "comparingToSelf", type: "BinaryExpression" }] },
- { code: "foo.bar().baz.qux >= foo.bar ().baz .qux", errors: [{ messageId: "comparingToSelf", type: "BinaryExpression" }] }
+ { code: "foo.bar().baz.qux >= foo.bar ().baz .qux", errors: [{ messageId: "comparingToSelf", type: "BinaryExpression" }] },
+ {
+ code: "class C { #field; foo() { this.#field === this.#field; } }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "comparingToSelf", type: "BinaryExpression" }]
+ }
]
});
// Tests
//------------------------------------------------------------------------------
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } });
ruleTester.run("no-setter-return", rule, {
valid: [
"class A { static set(val) { return 1; } }",
"({ set: set = function set(val) { return 1; } } = {})",
"({ set: set = (val) => 1 } = {})",
+ "class C { set; foo() { return 1; } }",
// not returning from the setter
"({ set foo(val) { function foo(val) { return 1; } } })",
const rule = require("../../../lib/rules/no-shadow-restricted-names"),
{ RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester();
ruleTester.run("no-shadow-restricted-names", rule, {
{ code: "function foo() { var top = 0; }", env: { browser: true } },
{ code: "var Object = 0;", options: [{ builtinGlobals: true }] },
{ code: "var top = 0;", options: [{ builtinGlobals: true }], env: { browser: true } },
- { code: "function foo(cb) { (function (cb) { cb(42); })(cb); }", options: [{ allow: ["cb"] }] }
+ { code: "function foo(cb) { (function (cb) { cb(42); })(cb); }", options: [{ allow: ["cb"] }] },
+ { code: "class C { foo; foo() { let foo; } }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { var x; } static { var x; } }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { let x; } static { let x; } }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { var x; { var x; /* redeclaration */ } } }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { { var x; } { var x; /* redeclaration */ } } }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { { let x; } { let x; } } }", parserOptions: { ecmaVersion: 2022 } }
],
invalid: [
{
line: 1,
column: 31
}]
+ },
+ {
+ code: "class C { static { let a; { let a; } } }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "noShadow",
+ data: {
+ name: "a",
+ shadowedLine: 1,
+ shadowedColumn: 24
+ },
+ type: "Identifier",
+ line: 1,
+ column: 33
+ }]
+ },
+ {
+ code: "class C { static { var C; } }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "noShadow",
+ data: {
+ name: "C",
+ shadowedLine: 1,
+ shadowedColumn: 7
+ },
+ type: "Identifier",
+ line: 1,
+ column: 24
+ }]
+ },
+ {
+ code: "class C { static { let C; } }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "noShadow",
+ data: {
+ name: "C",
+ shadowedLine: 1,
+ shadowedColumn: 7
+ },
+ type: "Identifier",
+ line: 1,
+ column: 24
+ }]
+ },
+ {
+ code: "var a; class C { static { var a; } }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "noShadow",
+ data: {
+ name: "a",
+ shadowedLine: 1,
+ shadowedColumn: 5
+ },
+ type: "Identifier",
+ line: 1,
+ column: 31
+ }]
+ },
+ {
+ code: "class C { static { var a; } } var a;",
+ options: [{ hoist: "all" }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "noShadow",
+ data: {
+ name: "a",
+ shadowedLine: 1,
+ shadowedColumn: 35
+ },
+ type: "Identifier",
+ line: 1,
+ column: 24
+ }]
+ },
+ {
+ code: "class C { static { let a; } } let a;",
+ options: [{ hoist: "all" }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "noShadow",
+ data: {
+ name: "a",
+ shadowedLine: 1,
+ shadowedColumn: 35
+ },
+ type: "Identifier",
+ line: 1,
+ column: 24
+ }]
+ },
+ {
+ code: "class C { static { var a; } } let a;",
+ options: [{ hoist: "all" }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "noShadow",
+ data: {
+ name: "a",
+ shadowedLine: 1,
+ shadowedColumn: 35
+ },
+ type: "Identifier",
+ line: 1,
+ column: 24
+ }]
+ },
+ {
+ code: "class C { static { var a; class D { static { var a; } } } }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "noShadow",
+ data: {
+ name: "a",
+ shadowedLine: 1,
+ shadowedColumn: 24
+ },
+ type: "Identifier",
+ line: 1,
+ column: 50
+ }]
+ },
+ {
+ code: "class C { static { let a; class D { static { let a; } } } }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "noShadow",
+ data: {
+ name: "a",
+ shadowedLine: 1,
+ shadowedColumn: 24
+ },
+ type: "Identifier",
+ line: 1,
+ column: 50
+ }]
}
]
});
// Tests
//------------------------------------------------------------------------------
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } });
ruleTester.run("no-this-before-super", rule, {
valid: [
}
}
}
- `
+ `,
+
+ // Class field initializers are always evaluated after `super()`.
+ "class C { field = this.toString(); }",
+ "class C extends B { field = this.foo(); }",
+ "class C extends B { field = this.foo(); constructor() { super(); } }",
+ "class C extends B { field = this.foo(); constructor() { } }" // < in this case, initializers are never evaluated.
],
invalid: [
"throw new foo();", // NewExpression
"throw foo.bar;", // MemberExpression
"throw foo[bar];", // MemberExpression
+ { code: "class C { #field; foo() { throw foo.#field; } }", parserOptions: { ecmaVersion: 2022 } }, // MemberExpression
"throw foo = new Error();", // AssignmentExpression with the `=` operator
{ code: "throw foo.bar ||= 'literal'", parserOptions: { ecmaVersion: 2021 } }, // AssignmentExpression with a logical operator
{ code: "throw foo[bar] ??= 'literal'", parserOptions: { ecmaVersion: 2021 } }, // AssignmentExpression with a logical operator
valid: [
"var a;",
{ code: "const foo = undefined", parserOptions: { ecmaVersion: 6 } },
- "var undefined = 5; var foo = undefined;"
+ "var undefined = 5; var foo = undefined;",
+
+ // doesn't apply to class fields
+ { code: "class C { field = undefined; }", parserOptions: { ecmaVersion: 2022 } }
+
],
invalid: [
{
{
code: "import.meta",
parserOptions: { ecmaVersion: 2020, sourceType: "module" }
+ },
+
+ // class static blocks
+ {
+ code: "let a; class C { static {} } a;",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "undef", data: { name: "a" } }]
+ },
+ {
+ code: "var a; class C { static {} } a;",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "undef", data: { name: "a" } }]
+ },
+ {
+ code: "a; class C { static {} } var a;",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "undef", data: { name: "a" } }]
+ },
+ {
+ code: "class C { static { C; } }",
+ parserOptions: { ecmaVersion: 2022, sourceType: "module" }
+ },
+ {
+ code: "const C = class { static { C; } }",
+ parserOptions: { ecmaVersion: 2022, sourceType: "module" }
+ },
+ {
+ code: "class C { static { a; } } var a;",
+ parserOptions: { ecmaVersion: 2022, sourceType: "module" }
+ },
+ {
+ code: "class C { static { a; } } let a;",
+ parserOptions: { ecmaVersion: 2022, sourceType: "module" }
+ },
+ {
+ code: "class C { static { var a; a; } }",
+ parserOptions: { ecmaVersion: 2022, sourceType: "module" }
+ },
+ {
+ code: "class C { static { a; var a; } }",
+ parserOptions: { ecmaVersion: 2022, sourceType: "module" }
+ },
+ {
+ code: "class C { static { a; { var a; } } }",
+ parserOptions: { ecmaVersion: 2022, sourceType: "module" }
+ },
+ {
+ code: "class C { static { let a; a; } }",
+ parserOptions: { ecmaVersion: 2022, sourceType: "module" }
+ },
+ {
+ code: "class C { static { a; let a; } }",
+ parserOptions: { ecmaVersion: 2022, sourceType: "module" }
+ },
+ {
+ code: "class C { static { function a() {} a; } }",
+ parserOptions: { ecmaVersion: 2022, sourceType: "module" }
+ },
+ {
+ code: "class C { static { a; function a() {} } }",
+ parserOptions: { ecmaVersion: 2022, sourceType: "module" }
}
],
invalid: [
ecmaVersion: 2018
},
errors: [{ messageId: "undef", data: { name: "b" } }]
+ },
+
+ // class static blocks
+ {
+ code: "class C { static { a; } }",
+ parserOptions: {
+ ecmaVersion: 2022
+ },
+ errors: [{ messageId: "undef", data: { name: "a" } }]
+ },
+ {
+ code: "class C { static { { let a; } a; } }",
+ parserOptions: {
+ ecmaVersion: 2022
+ },
+ errors: [{ messageId: "undef", data: { name: "a" }, column: 31 }]
+ },
+ {
+ code: "class C { static { { function a() {} } a; } }",
+ parserOptions: {
+ ecmaVersion: 2022
+ },
+ errors: [{ messageId: "undef", data: { name: "a" }, column: 40 }]
+ },
+ {
+ code: "class C { static { function foo() { var a; } a; } }",
+ parserOptions: {
+ ecmaVersion: 2022
+ },
+ errors: [{ messageId: "undef", data: { name: "a" }, column: 47 }]
+ },
+ {
+ code: "class C { static { var a; } static { a; } }",
+ parserOptions: {
+ ecmaVersion: 2022
+ },
+ errors: [{ messageId: "undef", data: { name: "a" }, column: 38 }]
+ },
+ {
+ code: "class C { static { let a; } static { a; } }",
+ parserOptions: {
+ ecmaVersion: 2022
+ },
+ errors: [{ messageId: "undef", data: { name: "a" }, column: 38 }]
+ },
+ {
+ code: "class C { static { function a(){} } static { a; } }",
+ parserOptions: {
+ ecmaVersion: 2022
+ },
+ errors: [{ messageId: "undef", data: { name: "a" }, column: 46 }]
+ },
+ {
+ code: "class C { static { var a; } foo() { a; } }",
+ parserOptions: {
+ ecmaVersion: 2022
+ },
+ errors: [{ messageId: "undef", data: { name: "a" }, column: 37 }]
+ },
+ {
+ code: "class C { static { let a; } foo() { a; } }",
+ parserOptions: {
+ ecmaVersion: 2022
+ },
+ errors: [{ messageId: "undef", data: { name: "a" }, column: 37 }]
+ },
+ {
+ code: "class C { static { var a; } [a]; }",
+ parserOptions: {
+ ecmaVersion: 2022
+ },
+ errors: [{ messageId: "undef", data: { name: "a" }, column: 30 }]
+ },
+ {
+ code: "class C { static { let a; } [a]; }",
+ parserOptions: {
+ ecmaVersion: 2022
+ },
+ errors: [{ messageId: "undef", data: { name: "a" }, column: 30 }]
+ },
+ {
+ code: "class C { static { function a() {} } [a]; }",
+ parserOptions: {
+ ecmaVersion: 2022
+ },
+ errors: [{ messageId: "undef", data: { name: "a" }, column: 39 }]
+ },
+ {
+ code: "class C { static { var a; } } a;",
+ parserOptions: {
+ ecmaVersion: 2022
+ },
+ errors: [{ messageId: "undef", data: { name: "a" }, column: 31 }]
}
]
});
{ code: "function foo([_bar] = []) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } },
{ code: "function foo( { _bar }) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } },
{ code: "function foo( { _bar = 0 } = {}) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } },
- { code: "function foo(...[_bar]) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 2016 } }
+ { code: "function foo(...[_bar]) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 2016 } },
+ { code: "class foo { _field; }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class foo { #_field; }", parserOptions: { ecmaVersion: 2022 } }
],
invalid: [
{ code: "var _foo = 1", errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_foo" }, type: "VariableDeclarator" }] },
{ code: "const foo = (_bar = 0) => {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "AssignmentPattern" }] },
{ code: "function foo(..._bar) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "RestElement" }] },
{ code: "const foo = { onClick(..._bar) { } }", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "RestElement" }] },
- { code: "const foo = (..._bar) => {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "RestElement" }] }
-
-
+ { code: "const foo = (..._bar) => {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "RestElement" }] },
+ {
+ code: "class foo { #_bar() {} }",
+ options: [{ enforceInMethodNames: true }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "#_bar" } }]
+ }, {
+ code: "class foo { #bar_() {} }",
+ options: [{ enforceInMethodNames: true }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "#bar_" } }]
+ }
]
});
const rule = require("../../../lib/rules/no-unexpected-multiline"),
{ RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester();
ruleTester.run("no-unexpected-multiline", rule, {
{
code: "var a = b?.\n [a, b, c].forEach(doSomething)",
parserOptions: { ecmaVersion: 2020 }
+ },
+
+ // Class fields
+ {
+ code: "class C { field1\n[field2]; }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { field1\n*gen() {} }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+
+ // ArrowFunctionExpression doesn't connect to computed properties.
+ code: "class C { field1 = () => {}\n[field2]; }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+
+ // ArrowFunctionExpression doesn't connect to binary operators.
+ code: "class C { field1 = () => {}\n*gen() {} }",
+ parserOptions: { ecmaVersion: 2022 }
}
],
invalid: [
messageId: "taggedTemplate"
}
]
+ },
+
+ // Class fields
+ {
+ code: "class C { field1 = obj\n[field2]; }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ {
+ line: 2,
+ column: 1,
+ endLine: 2,
+ endColumn: 2,
+ messageId: "property"
+ }
+ ]
+ },
+ {
+ code: "class C { field1 = function() {}\n[field2]; }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ {
+ line: 2,
+ column: 1,
+ endLine: 2,
+ endColumn: 2,
+ messageId: "property"
+ }
+ ]
}
+
+ // "class C { field1 = obj\n*gen() {} }" is syntax error: Unexpected token '{'
]
});
const rule = require("../../../lib/rules/no-unmodified-loop-condition"),
{ RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester();
ruleTester.run("no-unmodified-loop-condition", rule, {
parserOptions: {
ecmaVersion: 6
}
+ },
+ {
+ code: "class C { foo = reachable; }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { foo = reachable; constructor() {} }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C extends B { foo = reachable; }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C extends B { foo = reachable; constructor() { super(); } }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C extends B { static foo = reachable; constructor() {} }",
+ parserOptions: { ecmaVersion: 2022 }
}
],
invalid: [
endColumn: 22
}
]
+ },
+
+ /*
+ * If `extends` exists, constructor exists, and the constructor doesn't
+ * contain `super()`, then the fields are unreachable because the
+ * evaluation of `super()` initializes fields in that case.
+ * In most cases, such an instantiation throws runtime errors, but
+ * doesn't throw if the constructor returns a value.
+ */
+ {
+ code: "class C extends B { foo; constructor() {} }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "unreachableCode", column: 21, endColumn: 25 }]
+ },
+ {
+ code: "class C extends B { foo = unreachable + code; constructor() {} }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "unreachableCode", column: 21, endColumn: 46 }]
+ },
+ {
+ code: "class C extends B { foo; bar; constructor() {} }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "unreachableCode", column: 21, endColumn: 30 }]
+ },
+ {
+ code: "class C extends B { foo; constructor() {} bar; }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "unreachableCode", column: 21, endColumn: 25 },
+ { messageId: "unreachableCode", column: 43, endColumn: 47 }
+ ]
+ },
+ {
+ code: "(class extends B { foo; constructor() {} bar; })",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "unreachableCode", column: 20, endColumn: 24 },
+ { messageId: "unreachableCode", column: 42, endColumn: 46 }
+ ]
+ },
+ {
+ code: "class B extends A { x; constructor() { class C extends D { [super().x]; constructor() {} } } }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "unreachableCode", column: 60, endColumn: 72 }
+ ]
+ },
+ {
+ code: "class B extends A { x; constructor() { class C extends super().x { y; constructor() {} } } }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "unreachableCode", column: 68, endColumn: 70 }
+ ]
+ },
+ {
+ code: "class B extends A { x; static y; z; static q; constructor() {} }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "unreachableCode", column: 21, endColumn: 23 },
+ { messageId: "unreachableCode", column: 34, endColumn: 36 }
+ ]
}
]
});
"use strict";
const rule = require("../../../lib/rules/no-unsafe-optional-chaining");
-
const { RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const parserOptions = {
ecmaVersion: 2021,
sourceType: "module"
options: [{ enforceForJSX: true }],
parserOptions: { ecmaFeatures: { jsx: true } },
errors: [{ messageId: "unusedExpression", type: "ExpressionStatement" }]
+ },
+
+ // class static blocks do not have directive prologues
+ {
+ code: "class C { static { 'use strict'; } }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "unusedExpression", type: "ExpressionStatement" }]
+ },
+ {
+ code: "class C { static { \n'foo'\n'bar'\n } }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ {
+ messageId: "unusedExpression",
+ type: "ExpressionStatement",
+ line: 2
+ },
+ {
+ messageId: "unusedExpression",
+ type: "ExpressionStatement",
+ line: 3
+ }
+ ]
}
]
});
--- /dev/null
+/**
+ * @fileoverview Tests for no-unused-private-class-members rule.
+ * @author Tim van der Lippe
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const rule = require("../../../lib/rules/no-unused-private-class-members"),
+ { RuleTester } = require("../../../lib/rule-tester");
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } });
+
+/**
+ * Returns an expected error for defined-but-not-used private class member.
+ * @param {string} classMemberName The name of the class member
+ * @returns {Object} An expected error object
+ */
+function definedError(classMemberName) {
+ return {
+ messageId: "unusedPrivateClassMember",
+ data: {
+ classMemberName: `#${classMemberName}`
+ }
+ };
+}
+
+ruleTester.run("no-unused-private-class-members", rule, {
+ valid: [
+ "class Foo {}",
+ `class Foo {
+ publicMember = 42;
+}`,
+ `class Foo {
+ #usedMember = 42;
+ method() {
+ return this.#usedMember;
+ }
+}`,
+ `class Foo {
+ #usedMember = 42;
+ anotherMember = this.#usedMember;
+}`,
+ `class Foo {
+ #usedMember = 42;
+ foo() {
+ anotherMember = this.#usedMember;
+ }
+}`,
+ `class C {
+ #usedMember;
+
+ foo() {
+ bar(this.#usedMember += 1);
+ }
+}`,
+ `class Foo {
+ #usedMember = 42;
+ method() {
+ return someGlobalMethod(this.#usedMember);
+ }
+}`,
+ `class C {
+ #usedInOuterClass;
+
+ foo() {
+ return class {};
+ }
+
+ bar() {
+ return this.#usedInOuterClass;
+ }
+}`,
+ `class Foo {
+ #usedInForInLoop;
+ method() {
+ for (const bar in this.#usedInForInLoop) {
+
+ }
+ }
+}`,
+ `class Foo {
+ #usedInForOfLoop;
+ method() {
+ for (const bar of this.#usedInForOfLoop) {
+
+ }
+ }
+}`,
+ `class Foo {
+ #usedInAssignmentPattern;
+ method() {
+ [bar = 1] = this.#usedInAssignmentPattern;
+ }
+}`,
+ `class Foo {
+ #usedInArrayPattern;
+ method() {
+ [bar] = this.#usedInArrayPattern;
+ }
+}`,
+ `class Foo {
+ #usedInAssignmentPattern;
+ method() {
+ [bar] = this.#usedInAssignmentPattern;
+ }
+}`,
+ `class C {
+ #usedInObjectAssignment;
+
+ method() {
+ ({ [this.#usedInObjectAssignment]: a } = foo);
+ }
+}`,
+ `class C {
+ set #accessorWithSetterFirst(value) {
+ doSomething(value);
+ }
+ get #accessorWithSetterFirst() {
+ return something();
+ }
+ method() {
+ this.#accessorWithSetterFirst += 1;
+ }
+}`,
+ `class Foo {
+ set #accessorUsedInMemberAccess(value) {}
+
+ method(a) {
+ [this.#accessorUsedInMemberAccess] = a;
+ }
+}`,
+ `class C {
+ get #accessorWithGetterFirst() {
+ return something();
+ }
+ set #accessorWithGetterFirst(value) {
+ doSomething(value);
+ }
+ method() {
+ this.#accessorWithGetterFirst += 1;
+ }
+}`,
+ `class C {
+ #usedInInnerClass;
+
+ method(a) {
+ return class {
+ foo = a.#usedInInnerClass;
+ }
+ }
+}`,
+
+ //--------------------------------------------------------------------------
+ // Method definitions
+ //--------------------------------------------------------------------------
+ `class Foo {
+ #usedMethod() {
+ return 42;
+ }
+ anotherMethod() {
+ return this.#usedMethod();
+ }
+}`,
+ `class C {
+ set #x(value) {
+ doSomething(value);
+ }
+
+ foo() {
+ this.#x = 1;
+ }
+}`
+ ],
+ invalid: [
+ {
+ code: `class Foo {
+ #unusedMember = 5;
+}`,
+ errors: [definedError("unusedMember")]
+ },
+ {
+ code: `class First {}
+class Second {
+ #unusedMemberInSecondClass = 5;
+}`,
+ errors: [definedError("unusedMemberInSecondClass")]
+ },
+ {
+ code: `class First {
+ #unusedMemberInFirstClass = 5;
+}
+class Second {}`,
+ errors: [definedError("unusedMemberInFirstClass")]
+ },
+ {
+ code: `class First {
+ #firstUnusedMemberInSameClass = 5;
+ #secondUnusedMemberInSameClass = 5;
+}`,
+ errors: [definedError("firstUnusedMemberInSameClass"), definedError("secondUnusedMemberInSameClass")]
+ },
+ {
+ code: `class Foo {
+ #usedOnlyInWrite = 5;
+ method() {
+ this.#usedOnlyInWrite = 42;
+ }
+}`,
+ errors: [definedError("usedOnlyInWrite")]
+ },
+ {
+ code: `class Foo {
+ #usedOnlyInWriteStatement = 5;
+ method() {
+ this.#usedOnlyInWriteStatement += 42;
+ }
+}`,
+ errors: [definedError("usedOnlyInWriteStatement")]
+ },
+ {
+ code: `class C {
+ #usedOnlyInIncrement;
+
+ foo() {
+ this.#usedOnlyInIncrement++;
+ }
+}`,
+ errors: [definedError("usedOnlyInIncrement")]
+ },
+ {
+ code: `class C {
+ #unusedInOuterClass;
+
+ foo() {
+ return class {
+ #unusedInOuterClass;
+
+ bar() {
+ return this.#unusedInOuterClass;
+ }
+ };
+ }
+}`,
+ errors: [definedError("unusedInOuterClass")]
+ },
+ {
+ code: `class C {
+ #unusedOnlyInSecondNestedClass;
+
+ foo() {
+ return class {
+ #unusedOnlyInSecondNestedClass;
+
+ bar() {
+ return this.#unusedOnlyInSecondNestedClass;
+ }
+ };
+ }
+
+ baz() {
+ return this.#unusedOnlyInSecondNestedClass;
+ }
+
+ bar() {
+ return class {
+ #unusedOnlyInSecondNestedClass;
+ }
+ }
+}`,
+ errors: [definedError("unusedOnlyInSecondNestedClass")]
+ },
+
+ //--------------------------------------------------------------------------
+ // Unused method definitions
+ //--------------------------------------------------------------------------
+ {
+ code: `class Foo {
+ #unusedMethod() {}
+}`,
+ errors: [definedError("unusedMethod")]
+ },
+ {
+ code: `class Foo {
+ #unusedMethod() {}
+ #usedMethod() {
+ return 42;
+ }
+ publicMethod() {
+ return this.#usedMethod();
+ }
+}`,
+ errors: [definedError("unusedMethod")]
+ },
+ {
+ code: `class Foo {
+ set #unusedSetter(value) {}
+}`,
+ errors: [definedError("unusedSetter")]
+ },
+ {
+ code: `class Foo {
+ #unusedForInLoop;
+ method() {
+ for (this.#unusedForInLoop in bar) {
+
+ }
+ }
+}`,
+ errors: [definedError("unusedForInLoop")]
+ },
+ {
+ code: `class Foo {
+ #unusedForOfLoop;
+ method() {
+ for (this.#unusedForOfLoop of bar) {
+
+ }
+ }
+}`,
+ errors: [definedError("unusedForOfLoop")]
+ },
+ {
+ code: `class Foo {
+ #unusedInDestructuring;
+ method() {
+ ({ x: this.#unusedInDestructuring } = bar);
+ }
+}`,
+ errors: [definedError("unusedInDestructuring")]
+ },
+ {
+ code: `class Foo {
+ #unusedInRestPattern;
+ method() {
+ [...this.#unusedInRestPattern] = bar;
+ }
+}`,
+ errors: [definedError("unusedInRestPattern")]
+ },
+ {
+ code: `class Foo {
+ #unusedInAssignmentPattern;
+ method() {
+ [this.#unusedInAssignmentPattern = 1] = bar;
+ }
+}`,
+ errors: [definedError("unusedInAssignmentPattern")]
+ },
+ {
+ code: `class Foo {
+ #unusedInAssignmentPattern;
+ method() {
+ [this.#unusedInAssignmentPattern] = bar;
+ }
+}`,
+ errors: [definedError("unusedInAssignmentPattern")]
+ },
+ {
+ code: `class C {
+ #usedOnlyInTheSecondInnerClass;
+
+ method(a) {
+ return class {
+ #usedOnlyInTheSecondInnerClass;
+
+ method2(b) {
+ foo = b.#usedOnlyInTheSecondInnerClass;
+ }
+
+ method3(b) {
+ foo = b.#usedOnlyInTheSecondInnerClass;
+ }
+ }
+ }
+}`,
+ errors: [{
+ ...definedError("usedOnlyInTheSecondInnerClass"),
+ line: 2
+ }]
+ }
+ ]
+});
// Sequence Expressions (See https://github.com/eslint/eslint/issues/14325)
{ code: "let x = 0; foo = (0, x++);", parserOptions: { ecmaVersion: 6 } },
{ code: "let x = 0; foo = (0, x += 1);", parserOptions: { ecmaVersion: 6 } },
+ { code: "let x = 0; foo = (0, x = x + 1);", parserOptions: { ecmaVersion: 6 } },
// caughtErrors
{
parserOptions: { ecmaVersion: 2015 },
errors: [{ ...assignedError("x"), line: 1, column: 23 }]
},
+
+ // https://github.com/eslint/eslint/issues/14866
+ {
+ code: `let z = 0;
+ z = z + 1, z = 2;
+ `,
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ ...assignedError("z"), line: 2, column: 24 }]
+ },
+ {
+ code: `let z = 0;
+ z = z+1, z = 2;
+ z = 3;`,
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ ...assignedError("z"), line: 3, column: 13 }]
+ },
+ {
+ code: `let z = 0;
+ z = z+1, z = 2;
+ z = z+3;
+ `,
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ ...assignedError("z"), line: 3, column: 13 }]
+ },
+ {
+ code: "let x = 0; 0, x = x+1;",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ ...assignedError("x"), line: 1, column: 15 }]
+ },
+ {
+ code: "let x = 0; x = x+1, 0;",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ ...assignedError("x"), line: 1, column: 12 }]
+ },
+ {
+ code: "let x = 0; foo = ((0, x = x + 1), 0);",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ ...assignedError("x"), line: 1, column: 23 }]
+ },
+ {
+ code: "let x = 0; foo = (x = x+1, 0);",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ ...assignedError("x"), line: 1, column: 19 }]
+ },
+ {
+ code: "let x = 0; 0, (1, x=x+1);",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ ...assignedError("x"), line: 1, column: 19 }]
+ },
{
code: "(function ({ a, b }, { c } ) { return b; })();",
parserOptions: { ecmaVersion: 2015 },
ruleTester.run("no-use-before-define", rule, {
valid: [
+ "unresolved",
+ "Array",
+ "function foo () { arguments; }",
"var a=10; alert(a);",
"function b(a) { alert(a); }",
"Object.hasOwnProperty.call(a);",
"var foo = function() { foo(); };",
"var a; for (a in a) {}",
{ code: "var a; for (a of a) {}", parserOptions: { ecmaVersion: 6 } },
+ { code: "let a; class C { static { a; } }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { let a; a; } }", parserOptions: { ecmaVersion: 2022 } },
// Block-level bindings
{ code: "\"use strict\"; a(); { function a() {} }", parserOptions: { ecmaVersion: 6 } },
code: "var foo = () => bar; var bar;",
options: [{ variables: false }],
parserOptions: { ecmaVersion: 6 }
+ },
+ {
+ code: "class C { static { () => foo; let foo; } }",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+
+ // Tests related to class definition evaluation. These are not TDZ errors.
+ { code: "class C extends (class { method() { C; } }) {}", parserOptions: { ecmaVersion: 6 } },
+ { code: "(class extends (class { method() { C; } }) {});", parserOptions: { ecmaVersion: 6 } },
+ { code: "const C = (class extends (class { method() { C; } }) {});", parserOptions: { ecmaVersion: 6 } },
+ { code: "class C extends (class { field = C; }) {}", parserOptions: { ecmaVersion: 2022 } },
+ { code: "(class extends (class { field = C; }) {});", parserOptions: { ecmaVersion: 2022 } },
+ { code: "const C = (class extends (class { field = C; }) {});", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { [() => C](){} }", parserOptions: { ecmaVersion: 6 } },
+ { code: "(class C { [() => C](){} });", parserOptions: { ecmaVersion: 6 } },
+ { code: "const C = class { [() => C](){} };", parserOptions: { ecmaVersion: 6 } },
+ { code: "class C { static [() => C](){} }", parserOptions: { ecmaVersion: 6 } },
+ { code: "(class C { static [() => C](){} });", parserOptions: { ecmaVersion: 6 } },
+ { code: "const C = class { static [() => C](){} };", parserOptions: { ecmaVersion: 6 } },
+ { code: "class C { [() => C]; }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "(class C { [() => C]; });", parserOptions: { ecmaVersion: 2022 } },
+ { code: "const C = class { [() => C]; };", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static [() => C]; }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "(class C { static [() => C]; });", parserOptions: { ecmaVersion: 2022 } },
+ { code: "const C = class { static [() => C]; };", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { method() { C; } }", parserOptions: { ecmaVersion: 6 } },
+ { code: "(class C { method() { C; } });", parserOptions: { ecmaVersion: 6 } },
+ { code: "const C = class { method() { C; } };", parserOptions: { ecmaVersion: 6 } },
+ { code: "class C { static method() { C; } }", parserOptions: { ecmaVersion: 6 } },
+ { code: "(class C { static method() { C; } });", parserOptions: { ecmaVersion: 6 } },
+ { code: "const C = class { static method() { C; } };", parserOptions: { ecmaVersion: 6 } },
+ { code: "class C { field = C; }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "(class C { field = C; });", parserOptions: { ecmaVersion: 2022 } },
+ { code: "const C = class { field = C; };", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static field = C; }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "(class C { static field = C; });", parserOptions: { ecmaVersion: 2022 } }, // `const C = class { static field = C; };` is TDZ error
+ { code: "class C { static field = class { static field = C; }; }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "(class C { static field = class { static field = C; }; });", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { field = () => C; }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "(class C { field = () => C; });", parserOptions: { ecmaVersion: 2022 } },
+ { code: "const C = class { field = () => C; };", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static field = () => C; }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "(class C { static field = () => C; });", parserOptions: { ecmaVersion: 2022 } },
+ { code: "const C = class { static field = () => C; };", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { field = class extends C {}; }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "(class C { field = class extends C {}; });", parserOptions: { ecmaVersion: 2022 } },
+ { code: "const C = class { field = class extends C {}; }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static field = class extends C {}; }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "(class C { static field = class extends C {}; });", parserOptions: { ecmaVersion: 2022 } }, // `const C = class { static field = class extends C {}; };` is TDZ error
+ { code: "class C { static field = class { [C]; }; }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "(class C { static field = class { [C]; }; });", parserOptions: { ecmaVersion: 2022 } }, // `const C = class { static field = class { [C]; } };` is TDZ error
+ { code: "const C = class { static field = class { field = C; }; };", parserOptions: { ecmaVersion: 2022 } },
+ {
+ code: "class C { method() { a; } } let a;",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 6 }
+ },
+ {
+ code: "class C { static method() { a; } } let a;",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 6 }
+ },
+ {
+ code: "class C { field = a; } let a;", // `class C { static field = a; } let a;` is TDZ error
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { field = D; } class D {}", // `class C { static field = D; } class D {}` is TDZ error
+ options: [{ classes: false }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { field = class extends D {}; } class D {}", // `class C { static field = class extends D {}; } class D {}` is TDZ error
+ options: [{ classes: false }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { field = () => a; } let a;",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static field = () => a; } let a;",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { field = () => D; } class D {}",
+ options: [{ classes: false }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static field = () => D; } class D {}",
+ options: [{ classes: false }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static field = class { field = a; }; } let a;",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { C; } }", // `const C = class { static { C; } }` is TDZ error
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { C; } static {} static { C; } }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "(class C { static { C; } })",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { class D extends C {} } }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { (class { static { C } }) } }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { () => C; } }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "(class C { static { () => C; } })",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "const C = class { static { () => C; } }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { () => D; } } class D {}",
+ options: [{ classes: false }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { () => a; } } let a;",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "const C = class C { static { C.x; } }",
+ parserOptions: { ecmaVersion: 2022 }
}
],
invalid: [
messageId: "usedBeforeDefined",
data: { name: "x" }
}]
+ },
+
+ // Tests related to class definition evaluation. These are TDZ errors.
+ {
+ code: "class C extends C {}",
+ options: [{ classes: false }],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "const C = class extends C {};",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "class C extends (class { [C](){} }) {}",
+ options: [{ classes: false }],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "const C = class extends (class { [C](){} }) {};",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "class C extends (class { static field = C; }) {}",
+ options: [{ classes: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "const C = class extends (class { static field = C; }) {};",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "class C { [C](){} }",
+ options: [{ classes: false }],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "(class C { [C](){} });",
+ options: [{ classes: false }],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "const C = class { [C](){} };",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "class C { static [C](){} }",
+ options: [{ classes: false }],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "(class C { static [C](){} });",
+ options: [{ classes: false }],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "const C = class { static [C](){} };",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "class C { [C]; }",
+ options: [{ classes: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "(class C { [C]; });",
+ options: [{ classes: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "const C = class { [C]; };",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "class C { [C] = foo; }",
+ options: [{ classes: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "(class C { [C] = foo; });",
+ options: [{ classes: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "const C = class { [C] = foo; };",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "class C { static [C]; }",
+ options: [{ classes: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "(class C { static [C]; });",
+ options: [{ classes: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "const C = class { static [C]; };",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "class C { static [C] = foo; }",
+ options: [{ classes: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "(class C { static [C] = foo; });",
+ options: [{ classes: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "const C = class { static [C] = foo; };",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "const C = class { static field = C; };",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "const C = class { static field = class extends C {}; };",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "const C = class { static field = class { [C]; } };",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "const C = class { static field = class { static field = C; }; };",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "class C extends D {} class D {}",
+ options: [{ classes: false }],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "D" }
+ }]
+ },
+ {
+ code: "class C extends (class { [a](){} }) {} let a;",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "a" }
+ }]
+ },
+ {
+ code: "class C extends (class { static field = a; }) {} let a;",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "a" }
+ }]
+ },
+ {
+ code: "class C { [a]() {} } let a;",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "a" }
+ }]
+ },
+ {
+ code: "class C { static [a]() {} } let a;",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "a" }
+ }]
+ },
+ {
+ code: "class C { [a]; } let a;",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "a" }
+ }]
+ },
+ {
+ code: "class C { static [a]; } let a;",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "a" }
+ }]
+ },
+ {
+ code: "class C { [a] = foo; } let a;",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "a" }
+ }]
+ },
+ {
+ code: "class C { static [a] = foo; } let a;",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "a" }
+ }]
+ },
+ {
+ code: "class C { static field = a; } let a;",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "a" }
+ }]
+ },
+ {
+ code: "class C { static field = D; } class D {}",
+ options: [{ classes: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "D" }
+ }]
+ },
+ {
+ code: "class C { static field = class extends D {}; } class D {}",
+ options: [{ classes: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "D" }
+ }]
+ },
+ {
+ code: "class C { static field = class { [a](){} } } let a;",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "a" }
+ }]
+ },
+ {
+ code: "class C { static field = class { static field = a; }; } let a;",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "a" }
+ }]
+ },
+ {
+ code: "const C = class { static { C; } };",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "const C = class { static { (class extends C {}); } };",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "C" }
+ }]
+ },
+ {
+ code: "class C { static { a; } } let a;",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "a" }
+ }]
+ },
+ {
+ code: "class C { static { D; } } class D {}",
+ options: [{ classes: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "D" }
+ }]
+ },
+ {
+ code: "class C { static { (class extends D {}); } } class D {}",
+ options: [{ classes: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "D" }
+ }]
+ },
+ {
+ code: "class C { static { (class { [a](){} }); } } let a;",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "a" }
+ }]
+ },
+ {
+ code: "class C { static { (class { static field = a; }); } } let a;",
+ options: [{ variables: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "usedBeforeDefined",
+ data: { name: "a" }
+ }]
}
+
+ /*
+ * TODO(mdjermanovic): Add the following test cases once https://github.com/eslint/eslint-scope/issues/59 gets fixed:
+ * {
+ * code: "(class C extends C {});",
+ * options: [{ classes: false }],
+ * parserOptions: { ecmaVersion: 6 },
+ * errors: [{
+ * messageId: "usedBeforeDefined",
+ * data: { name: "C" }
+ * }]
+ * },
+ * {
+ * code: "(class C extends (class { [C](){} }) {});",
+ * options: [{ classes: false }],
+ * parserOptions: { ecmaVersion: 6 },
+ * errors: [{
+ * messageId: "usedBeforeDefined",
+ * data: { name: "C" }
+ * }]
+ * },
+ * {
+ * code: "(class C extends (class { static field = C; }) {});",
+ * options: [{ classes: false }],
+ * parserOptions: { ecmaVersion: 2022 },
+ * errors: [{
+ * messageId: "usedBeforeDefined",
+ * data: { name: "C" }
+ * }]
+ * }
+ */
]
});
{
code: "obj?.foo.bar.call(obj.foo, 1, 2);",
parserOptions: { ecmaVersion: 2020 }
+ },
+
+ // Private members
+ {
+ code: "class C { #call; wrap(foo) { foo.#call(undefined, 1, 2); } }",
+ parserOptions: { ecmaVersion: 2022 }
}
],
invalid: [
// Tests
//------------------------------------------------------------------------------
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } });
ruleTester.run("no-useless-computed-key", rule, {
valid: [
{ code: "(class { ['x']() {} })", options: [{ enforceForClassMembers: false }] },
{ code: "class Foo { static ['constructor']() {} }", options: [{ enforceForClassMembers: false }] },
{ code: "class Foo { ['prototype']() {} }", options: [{ enforceForClassMembers: false }] },
+ { code: "class Foo { a }", options: [{ enforceForClassMembers: true }] },
+ { code: "class Foo { ['constructor'] }", options: [{ enforceForClassMembers: true }] },
+ { code: "class Foo { static ['constructor'] }", options: [{ enforceForClassMembers: true }] },
+ { code: "class Foo { static ['prototype'] }", options: [{ enforceForClassMembers: true }] },
/*
* Well-known browsers throw syntax error bigint literals on property names,
data: { property: "2" },
type: "Property"
}]
+ }, {
+ code: "({ ['constructor']: 1 })",
+ output: "({ 'constructor': 1 })",
+ errors: [{
+ messageId: "unnecessarilyComputedProperty",
+ data: { property: "'constructor'" },
+ type: "Property"
+ }]
+ }, {
+ code: "({ ['prototype']: 1 })",
+ output: "({ 'prototype': 1 })",
+ errors: [{
+ messageId: "unnecessarilyComputedProperty",
+ data: { property: "'prototype'" },
+ type: "Property"
+ }]
}, {
code: "class Foo { ['0']() {} }",
output: "class Foo { '0'() {} }",
data: { property: "'x'" },
type: "MethodDefinition"
}]
+ }, {
+ code: "(class { ['__proto__']() {} })",
+ output: "(class { '__proto__'() {} })",
+ options: [{ enforceForClassMembers: true }],
+ errors: [{
+ messageId: "unnecessarilyComputedProperty",
+ data: { property: "'__proto__'" },
+ type: "MethodDefinition"
+ }]
+ }, {
+ code: "(class { static ['__proto__']() {} })",
+ output: "(class { static '__proto__'() {} })",
+ options: [{ enforceForClassMembers: true }],
+ errors: [{
+ messageId: "unnecessarilyComputedProperty",
+ data: { property: "'__proto__'" },
+ type: "MethodDefinition"
+ }]
}, {
code: "(class { static ['constructor']() {} })",
output: "(class { static 'constructor'() {} })",
data: { property: "'prototype'" },
type: "MethodDefinition"
}]
+ }, {
+ code: "class Foo { ['0'] }",
+ output: "class Foo { '0' }",
+ options: [{ enforceForClassMembers: true }],
+ errors: [{
+ messageId: "unnecessarilyComputedProperty",
+ data: { property: "'0'" },
+ type: "PropertyDefinition"
+ }]
+ }, {
+ code: "class Foo { ['0'] = 0 }",
+ output: "class Foo { '0' = 0 }",
+ options: [{ enforceForClassMembers: true }],
+ errors: [{
+ messageId: "unnecessarilyComputedProperty",
+ data: { property: "'0'" },
+ type: "PropertyDefinition"
+ }]
+ }, {
+ code: "class Foo { static[0] }",
+ output: "class Foo { static 0 }",
+ options: [{ enforceForClassMembers: true }],
+ errors: [{
+ messageId: "unnecessarilyComputedProperty",
+ data: { property: "0" },
+ type: "PropertyDefinition"
+ }]
+ }, {
+ code: "class Foo { ['#foo'] }",
+ output: "class Foo { '#foo' }",
+ options: [{ enforceForClassMembers: true }],
+ errors: [{
+ messageId: "unnecessarilyComputedProperty",
+ data: { property: "'#foo'" },
+ type: "PropertyDefinition"
+ }]
+ }, {
+ code: "(class { ['__proto__'] })",
+ output: "(class { '__proto__' })",
+ options: [{ enforceForClassMembers: true }],
+ errors: [{
+ messageId: "unnecessarilyComputedProperty",
+ data: { property: "'__proto__'" },
+ type: "PropertyDefinition"
+ }]
+ }, {
+ code: "(class { static ['__proto__'] })",
+ output: "(class { static '__proto__' })",
+ options: [{ enforceForClassMembers: true }],
+ errors: [{
+ messageId: "unnecessarilyComputedProperty",
+ data: { property: "'__proto__'" },
+ type: "PropertyDefinition"
+ }]
+ }, {
+ code: "(class { ['prototype'] })",
+ output: "(class { 'prototype' })",
+ options: [{ enforceForClassMembers: true }],
+ errors: [{
+ messageId: "unnecessarilyComputedProperty",
+ data: { property: "'prototype'" },
+ type: "PropertyDefinition"
+ }]
}
]
});
//------------------------------------------------------------------------------
const rule = require("../../../lib/rules/no-useless-concat"),
-
{ RuleTester } = require("../../../lib/rule-tester");
const rule = require("../../../lib/rules/no-useless-escape"),
{ RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester();
ruleTester.run("no-useless-escape", rule, {
const rule = require("../../../lib/rules/no-useless-rename"),
{ RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6, sourceType: "module" } });
ruleTester.run("no-useless-rename", rule, {
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
ruleTester.run("one-var", rule, {
valid: [
"function foo() { var bar = true; }",
{
code: "var foo = 1;\nlet bar = function() { var x; };\nvar baz = 2;",
options: [{ var: "never" }]
+ },
+
+ // class static blocks
+ {
+ code: "class C { static { var a; let b; const c = 0; } }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "const a = 0; class C { static { const b = 0; } }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { const b = 0; } } const a = 0; ",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "let a; class C { static { let b; } }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { let b; } } let a;",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "var a; class C { static { var b; } }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { var b; } } var a; ",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "var a; class C { static { if (foo) { var b; } } }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { if (foo) { var b; } } } var a; ",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { const a = 0; if (foo) { const b = 0; } } }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { let a; if (foo) { let b; } } }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { const a = 0; const b = 0; } }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { let a; let b; } }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { var a; var b; } }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { let a; foo; let b; } }",
+ options: ["consecutive"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { let a; const b = 0; let c; } }",
+ options: ["consecutive"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { var a; foo; var b; } }",
+ options: ["consecutive"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { var a; let b; var c; } }",
+ options: ["consecutive"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { let a; if (foo) { let b; } } }",
+ options: ["consecutive"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { if (foo) { let b; } let a; } }",
+ options: ["consecutive"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { const a = 0; if (foo) { const b = 0; } } }",
+ options: ["consecutive"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { if (foo) { const b = 0; } const a = 0; } }",
+ options: ["consecutive"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { var a; if (foo) var b; } }",
+ options: ["consecutive"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { if (foo) var b; var a; } }",
+ options: ["consecutive"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { if (foo) { var b; } var a; } }",
+ options: ["consecutive"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { let a; let b = 0; } }",
+ options: [{ initialized: "consecutive" }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { var a; var b = 0; } }",
+ options: [{ initialized: "consecutive" }],
+ parserOptions: { ecmaVersion: 2022 }
}
],
invalid: [
data: { type: "var" },
type: "VariableDeclaration"
}]
+ },
+
+ // class static blocks
+ {
+ code: "class C { static { let x, y; } }",
+ output: "class C { static { let x; let y; } }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "split",
+ data: { type: "let" },
+ type: "VariableDeclaration"
+ }]
+ },
+ {
+ code: "class C { static { var x, y; } }",
+ output: "class C { static { var x; var y; } }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "split",
+ data: { type: "var" },
+ type: "VariableDeclaration"
+ }]
+ },
+ {
+ code: "class C { static { let x; let y; } }",
+ output: "class C { static { let x, y; } }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "combine",
+ data: { type: "let" },
+ type: "VariableDeclaration"
+ }]
+ },
+ {
+ code: "class C { static { var x; var y; } }",
+ output: "class C { static { var x, y; } }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "combine",
+ data: { type: "var" },
+ type: "VariableDeclaration"
+ }]
+ },
+ {
+ code: "class C { static { let x; foo; let y; } }",
+ output: null,
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "combine",
+ data: { type: "let" },
+ type: "VariableDeclaration"
+ }]
+ },
+ {
+ code: "class C { static { var x; foo; var y; } }",
+ output: null,
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "combine",
+ data: { type: "var" },
+ type: "VariableDeclaration"
+ }]
+ },
+ {
+ code: "class C { static { var x; if (foo) { var y; } } }",
+ output: null,
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "combine",
+ data: { type: "var" },
+ type: "VariableDeclaration"
+ }]
+ },
+ {
+ code: "class C { static { let x; let y; } }",
+ output: "class C { static { let x, y; } }",
+ options: ["consecutive"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "combine",
+ data: { type: "let" },
+ type: "VariableDeclaration"
+ }]
+ },
+ {
+ code: "class C { static { var x; var y; } }",
+ output: "class C { static { var x, y; } }",
+ options: ["consecutive"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "combine",
+ data: { type: "var" },
+ type: "VariableDeclaration"
+ }]
+ },
+ {
+ code: "class C { static { let a = 0; let b = 1; } }",
+ output: "class C { static { let a = 0, b = 1; } }",
+ options: [{ initialized: "consecutive" }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "combineInitialized",
+ data: { type: "let" },
+ type: "VariableDeclaration"
+ }]
+ },
+ {
+ code: "class C { static { var a = 0; var b = 1; } }",
+ output: "class C { static { var a = 0, b = 1; } }",
+ options: [{ initialized: "consecutive" }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "combineInitialized",
+ data: { type: "var" },
+ type: "VariableDeclaration"
+ }]
}
]
});
// Tests
//------------------------------------------------------------------------------
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2021 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } });
const EXPECTED_OPERATOR_ASSIGNMENT = [{ messageId: "replaced", type: "AssignmentExpression" }];
const UNEXPECTED_OPERATOR_ASSIGNMENT = [{ messageId: "unexpected", type: "AssignmentExpression" }];
code: "this.x = foo.this.x + y",
options: ["always"]
},
+ "const foo = 0; class C { foo = foo + 1; }",
// does not check logical operators
{
{ code: "\n1 + 1", options: ["none"] },
{ code: "1 + 1\n", options: ["none"] },
{ code: "answer = everything ? 42 : foo;", options: ["none"] },
+ { code: "(a\n) + (\nb)", options: ["none"] },
{ code: "answer = everything \n?\n 42 : foo;", options: [null, { overrides: { "?": "ignore" } }] },
{ code: "answer = everything ? 42 \n:\n foo;", options: [null, { overrides: { ":": "ignore" } }] },
code: "a ??= \n b",
options: ["after", { overrides: { "??": "before" } }],
parserOptions: { ecmaVersion: 2021 }
+ },
+
+ // class fields
+ {
+ code: "class C { foo =\n0 }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { foo\n= 0 }",
+ options: ["before"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { [foo\n]= 0 }",
+ options: ["before"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { [foo]\n= 0 }",
+ options: ["before"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { [foo\n]\n= 0 }",
+ options: ["before"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { [foo\n]= 0 }",
+ options: ["after"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { [foo\n]=\n0 }",
+ options: ["after"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { [foo\n]= 0 }",
+ options: ["none"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { foo\n=\n0 }",
+ options: ["none", { overrides: { "=": "ignore" } }],
+ parserOptions: { ecmaVersion: 2022 }
}
],
endLine: 2,
endColumn: 4
}]
+ },
+
+ // class fields
+ {
+ code: "class C { a\n= 0; }",
+ output: "class C { a =\n0; }",
+ options: ["after"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "operatorAtEnd",
+ data: { operator: "=" },
+ type: "PropertyDefinition",
+ line: 2,
+ column: 1,
+ endLine: 2,
+ endColumn: 2
+ }]
+ },
+ {
+ code: "class C { a =\n0; }",
+ output: "class C { a\n= 0; }",
+ options: ["before"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "operatorAtBeginning",
+ data: { operator: "=" },
+ type: "PropertyDefinition",
+ line: 1,
+ column: 13,
+ endLine: 1,
+ endColumn: 14
+ }]
+ },
+ {
+ code: "class C { a =\n0; }",
+ output: "class C { a =0; }",
+ options: ["none"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "noLinebreak",
+ data: { operator: "=" },
+ type: "PropertyDefinition",
+ line: 1,
+ column: 13,
+ endLine: 1,
+ endColumn: 14
+ }]
+ },
+ {
+ code: "class C { [a]\n= 0; }",
+ output: "class C { [a] =\n0; }",
+ options: ["after"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "operatorAtEnd",
+ data: { operator: "=" },
+ type: "PropertyDefinition",
+ line: 2,
+ column: 1,
+ endLine: 2,
+ endColumn: 2
+ }]
+ },
+ {
+ code: "class C { [a] =\n0; }",
+ output: "class C { [a]\n= 0; }",
+ options: ["before"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "operatorAtBeginning",
+ data: { operator: "=" },
+ type: "PropertyDefinition",
+ line: 1,
+ column: 15,
+ endLine: 1,
+ endColumn: 16
+ }]
+ },
+ {
+ code: "class C { [a]\n =0; }",
+ output: "class C { [a] =0; }",
+ options: ["none"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "noLinebreak",
+ data: { operator: "=" },
+ type: "PropertyDefinition",
+ line: 2,
+ column: 2,
+ endLine: 2,
+ endColumn: 3
+ }]
}
]
});
{ code: "class A{\nfoo(){}\n}", options: ["never"], parserOptions: { ecmaVersion: 6 } },
{ code: "class A{\nfoo(){}\n}", options: [{ classes: "never" }], parserOptions: { ecmaVersion: 6 } },
+ { code: "class A{\n\nfoo;\n\n}", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class A{\nfoo;\n}", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+
// Ignore block statements if not configured
{ code: "{\na();\n}", options: [{ switches: "always" }] },
{ code: "{\n\na();\n\n}", options: [{ switches: "never" }] },
// Ignore class statements if not configured
{ code: "class A{\nfoo(){}\n}", options: [{ blocks: "always" }], parserOptions: { ecmaVersion: 6 } },
- { code: "class A{\n\nfoo(){}\n\n}", options: [{ blocks: "never" }], parserOptions: { ecmaVersion: 6 } }
+ { code: "class A{\n\nfoo(){}\n\n}", options: [{ blocks: "never" }], parserOptions: { ecmaVersion: 6 } },
+
+ // class static blocks
+ {
+ code: "class C {\n\n static {\n\nfoo;\n\n} \n\n}",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C {\n\n static {// comment\n\nfoo;\n\n/* comment */} \n\n}",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C {\n\n static {\n\n// comment\nfoo;\n// comment\n\n} \n\n}",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C {\n\n static {\n\n// comment\n\nfoo;\n\n// comment\n\n} \n\n}",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C {\n\n static { foo; } \n\n}",
+ options: ["always", { allowSingleLineBlocks: true }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C {\n\n static\n { foo; } \n\n}",
+ options: ["always", { allowSingleLineBlocks: true }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C {\n\n static {} static {\n} static {\n\n} \n\n}", // empty blocks are ignored
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C {\n\n static\n\n { foo; } \n\n}",
+ options: ["always", { allowSingleLineBlocks: true }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C {\n static {\n\nfoo;\n\n} \n}",
+ options: [{ blocks: "always", classes: "never" }], // "blocks" applies to static blocks
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C {\n static {\nfoo;\n} \n}",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C {\n static {foo;} \n}",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C {\n static\n {\nfoo;\n} \n}",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C {\n static\n\n {\nfoo;\n} \n}",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C {\n static\n\n {foo;} \n}",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C {\n static {// comment\nfoo;\n/* comment */} \n}",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C {\n static {\n// comment\nfoo;\n// comment\n} \n}",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C {\n static {} static {\n} static {\n\n} \n}", // empty blocks are ignored
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C {\n\n static {\nfoo;\n} \n\n}",
+ options: [{ blocks: "never", classes: "always" }], // "blocks" applies to static blocks
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C {\n\n static {\nfoo;\n} static {\n\nfoo;\n\n} \n\n}",
+ options: [{ classes: "always" }], // if there's no "blocks" in the object option, static blocks are ignored
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C {\n static {\nfoo;\n} static {\n\nfoo;\n\n} \n}",
+ options: [{ classes: "never" }], // if there's no "blocks" in the object option, static blocks are ignored
+ parserOptions: { ecmaVersion: 2022 }
+ }
],
invalid: [
output: "function foo() { /* a\n */ /* b\n */\n\n bar;\n\n/* c\n *//* d\n */}",
options: ["always"],
errors: [{ messageId: "alwaysPadBlock" }, { messageId: "alwaysPadBlock" }]
+ },
+ {
+ code: "class A{\nfoo;\n}",
+ output: "class A{\n\nfoo;\n\n}",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "alwaysPadBlock" }, { messageId: "alwaysPadBlock" }]
+ },
+ {
+ code: "class A{\n\nfoo;\n\n}",
+ output: "class A{\nfoo;\n}",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "neverPadBlock" }, { messageId: "neverPadBlock" }]
+ },
+
+ // class static blocks
+ {
+ code: "class C {\n\n static {\nfoo;\n\n} \n\n}",
+ output: "class C {\n\n static {\n\nfoo;\n\n} \n\n}",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "alwaysPadBlock" }]
+ },
+ {
+ code: "class C {\n\n static\n {\nfoo;\n\n} \n\n}",
+ output: "class C {\n\n static\n {\n\nfoo;\n\n} \n\n}",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "alwaysPadBlock" }]
+ },
+ {
+ code: "class C {\n\n static\n\n {\nfoo;\n\n} \n\n}",
+ output: "class C {\n\n static\n\n {\n\nfoo;\n\n} \n\n}",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "alwaysPadBlock" }]
+ },
+ {
+ code: "class C {\n\n static {\n\nfoo;\n} \n\n}",
+ output: "class C {\n\n static {\n\nfoo;\n\n} \n\n}",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "alwaysPadBlock" }]
+ },
+ {
+ code: "class C {\n\n static {foo;} \n\n}",
+ output: "class C {\n\n static {\nfoo;\n} \n\n}", // this is still not padded, the subsequent fix below will add another pair of `\n`.
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "alwaysPadBlock" },
+ { messageId: "alwaysPadBlock" }
+ ]
+ },
+ {
+ code: "class C {\n\n static {\nfoo;\n} \n\n}",
+ output: "class C {\n\n static {\n\nfoo;\n\n} \n\n}",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "alwaysPadBlock" },
+ { messageId: "alwaysPadBlock" }
+ ]
+ },
+ {
+ code: "class C {\n\n static {// comment\nfoo;\n/* comment */} \n\n}",
+ output: "class C {\n\n static {// comment\n\nfoo;\n\n/* comment */} \n\n}",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "alwaysPadBlock" },
+ { messageId: "alwaysPadBlock" }
+ ]
+ },
+ {
+ code: "class C {\n\n static {\n// comment\nfoo;\n// comment\n} \n\n}",
+ output: "class C {\n\n static {\n\n// comment\nfoo;\n// comment\n\n} \n\n}",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "alwaysPadBlock" },
+ { messageId: "alwaysPadBlock" }
+ ]
+ },
+ {
+ code: "class C {\n\n static {\n// comment\n\nfoo;\n\n// comment\n} \n\n}",
+ output: "class C {\n\n static {\n\n// comment\n\nfoo;\n\n// comment\n\n} \n\n}",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "alwaysPadBlock" },
+ { messageId: "alwaysPadBlock" }
+ ]
+ },
+ {
+ code: "class C {\n static {\nfoo;\n} \n}",
+ output: "class C {\n static {\n\nfoo;\n\n} \n}",
+ options: [{ blocks: "always", classes: "never" }], // "blocks" applies to static blocks
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "alwaysPadBlock" },
+ { messageId: "alwaysPadBlock" }
+ ]
+ },
+ {
+ code: "class C {\n static {\n\nfoo;\n} \n}",
+ output: "class C {\n static {\nfoo;\n} \n}",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "neverPadBlock" }]
+ },
+ {
+ code: "class C {\n static\n {\n\nfoo;\n} \n}",
+ output: "class C {\n static\n {\nfoo;\n} \n}",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "neverPadBlock" }]
+ },
+ {
+ code: "class C {\n static\n\n {\n\nfoo;\n} \n}",
+ output: "class C {\n static\n\n {\nfoo;\n} \n}",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "neverPadBlock" }]
+ },
+ {
+ code: "class C {\n static {\nfoo;\n\n} \n}",
+ output: "class C {\n static {\nfoo;\n} \n}",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "neverPadBlock" }]
+ },
+ {
+ code: "class C {\n static {\n\nfoo;\n\n} \n}",
+ output: "class C {\n static {\nfoo;\n} \n}",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "neverPadBlock" },
+ { messageId: "neverPadBlock" }
+ ]
+ },
+ {
+ code: "class C {\n static {// comment\n\nfoo;\n\n/* comment */} \n}",
+ output: "class C {\n static {// comment\nfoo;\n/* comment */} \n}",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "neverPadBlock" },
+ { messageId: "neverPadBlock" }
+ ]
+ },
+ {
+ code: "class C {\n static {\n\n// comment\nfoo;\n// comment\n\n} \n}",
+ output: "class C {\n static {\n// comment\nfoo;\n// comment\n} \n}",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "neverPadBlock" },
+ { messageId: "neverPadBlock" }
+ ]
+ },
+ {
+ code: "class C {\n\n static {\n\nfoo;\n\n} \n\n}",
+ output: "class C {\n\n static {\nfoo;\n} \n\n}",
+ options: [{ blocks: "never", classes: "always" }], // "blocks" applies to static blocks
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "neverPadBlock" },
+ { messageId: "neverPadBlock" }
+ ]
}
]
});
options: [
{ blankLine: "always", prev: "block-like", next: "block-like" }
]
+ },
+
+ // class static blocks
+ {
+ code: "class C {\n static {\n let x;\n\n foo();\n }\n }",
+ options: [
+ { blankLine: "always", prev: "let", next: "expression" }
+ ],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C {\n static {\n let x;\n foo();\n }\n }",
+ options: [
+ { blankLine: "never", prev: "let", next: "expression" }
+ ],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C {\n static {\n let x;\n foo();\n\n const y = 1;\n }\n }",
+ options: [
+ { blankLine: "always", prev: "expression", next: "const" }
+ ],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C {\n static {\n let x;\n foo();\n const y = 1;\n }\n }",
+ options: [
+ { blankLine: "never", prev: "expression", next: "const" }
+ ],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C {\n static {\n let x;\n foo();\n\n const y = 1;\n const z = 1;\n }\n }",
+ options: [
+ { blankLine: "always", prev: "expression", next: "const" }
+ ],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C {\n static {\n let x;\n foo();\n const y = 1;\n const z = 1;\n }\n }",
+ options: [
+ { blankLine: "never", prev: "expression", next: "const" }
+ ],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C {\n static {\n let a = 0;\n let b =0;\n\n bar();\n }\n }",
+ options: [
+ { blankLine: "always", prev: ["const", "let", "var"], next: "*" },
+ { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"] }
+ ],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { let x; { let y; } let z; } }",
+ options: [
+ { blankLine: "always", prev: "let", next: "let" }
+ ],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { method() { let x; } static { let y; } }",
+ options: [
+ { blankLine: "always", prev: "let", next: "let" }
+ ],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { let y; } method() { let x; } }",
+ options: [
+ { blankLine: "always", prev: "let", next: "let" }
+ ],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { let x; } static { let y; } }",
+ options: [
+ { blankLine: "always", prev: "let", next: "let" }
+ ],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "let x; class C { static { let y; } }",
+ options: [
+ { blankLine: "always", prev: "let", next: "let" }
+ ],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { let x; } } let y;",
+ options: [
+ { blankLine: "always", prev: "let", next: "let" }
+ ],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { let x; } }",
+ options: [
+ { blankLine: "always", prev: "class", next: "let" }
+ ],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { 'use strict'; let x; } }", // 'use strict'; is "espression", because class static blocks don't have directives
+ options: [
+ { blankLine: "always", prev: "directive", next: "let" }
+ ],
+ parserOptions: { ecmaVersion: 2022 }
}
],
invalid: [
{ messageId: "expectedBlankLine" },
{ messageId: "expectedBlankLine" }
]
+ },
+
+ // class static blocks
+ {
+ code: "class C {\n static {\n let x;\n foo();\n }\n }",
+ output: "class C {\n static {\n let x;\n\n foo();\n }\n }",
+ options: [
+ { blankLine: "always", prev: "let", next: "expression" }
+ ],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "expectedBlankLine" }]
+ },
+ {
+ code: "class C {\n static {\n let x;\n\n foo();\n }\n }",
+ output: "class C {\n static {\n let x;\n foo();\n }\n }",
+ options: [
+ { blankLine: "never", prev: "let", next: "expression" }
+ ],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "unexpectedBlankLine" }]
+ },
+ {
+ code: "class C {\n static {\n let x;\n foo();\n const y = 1;\n }\n }",
+ output: "class C {\n static {\n let x;\n foo();\n\n const y = 1;\n }\n }",
+ options: [
+ { blankLine: "always", prev: "expression", next: "const" }
+ ],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "expectedBlankLine" }]
+ },
+ {
+ code: "class C {\n static {\n let x;\n foo();\n\n const y = 1;\n }\n }",
+ output: "class C {\n static {\n let x;\n foo();\n const y = 1;\n }\n }",
+ options: [
+ { blankLine: "never", prev: "expression", next: "const" }
+ ],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "unexpectedBlankLine" }]
+ },
+ {
+ code: "class C {\n static {\n let x;\n foo();\n const y = 1;\n const z = 1;\n }\n }",
+ output: "class C {\n static {\n let x;\n foo();\n\n const y = 1;\n const z = 1;\n }\n }",
+ options: [
+ { blankLine: "always", prev: "expression", next: "const" }
+ ],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "expectedBlankLine" }]
+ },
+ {
+ code: "class C {\n static {\n let x;\n foo();\n\n const y = 1;\n const z = 1;\n }\n }",
+ output: "class C {\n static {\n let x;\n foo();\n const y = 1;\n const z = 1;\n }\n }",
+ options: [
+ { blankLine: "never", prev: "expression", next: "const" }
+ ],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "unexpectedBlankLine" }]
+ },
+ {
+ code: "class C {\n static {\n let a = 0;\n bar();\n }\n }",
+ output: "class C {\n static {\n let a = 0;\n\n bar();\n }\n }",
+ options: [
+ { blankLine: "always", prev: ["const", "let", "var"], next: "*" },
+ { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"] }
+ ],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "expectedBlankLine" }]
+ },
+ {
+ code: "class C { static { let x; { let y; let z; } let q; } }",
+ output: "class C { static { let x; { let y;\n\n let z; } let q; } }",
+ options: [
+ { blankLine: "always", prev: "let", next: "let" }
+ ],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "expectedBlankLine" }]
+ },
+ {
+ code: "class C { static { { let x; } let y; let z; } }",
+ output: "class C { static { { let x; } let y;\n\n let z; } }",
+ options: [
+ { blankLine: "always", prev: "let", next: "let" }
+ ],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "expectedBlankLine" }]
+ },
+ {
+ code: "class C { static { foo(); if (bar) {} } }",
+ output: "class C { static { foo();\n\n if (bar) {} } }",
+ options: [
+ { blankLine: "always", prev: "expression", next: "block-like" }
+ ],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "expectedBlankLine" }]
+ },
+ {
+ code: "class C { static { let x; } } let y;",
+ output: "class C { static { let x; } }\n\n let y;",
+ options: [
+ { blankLine: "always", prev: "class", next: "let" }
+ ],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "expectedBlankLine" }]
+ },
+ {
+ code: "class C { static { 'use strict'; let x; } }", // 'use strict'; is "espression", because class static blocks don't have directives
+ output: "class C { static { 'use strict';\n\n let x; } }",
+ options: [
+ { blankLine: "always", prev: "expression", next: "let" }
+ ],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "expectedBlankLine" }]
}
]
});
// https://github.com/eslint/eslint/issues/10520
"const x = [1,2]; let y; [,y] = x; y = 0;",
- "const x = [1,2,3]; let y, z; [y,,z] = x; y = 0; z = 0;"
+ "const x = [1,2,3]; let y, z; [y,,z] = x; y = 0; z = 0;",
+
+ {
+ code: "class C { static { let a = 1; a = 2; } }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { let a; a = 1; a = 2; } }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "let a; class C { static { a = 1; } }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { let a; if (foo) { a = 1; } } }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { let a; if (foo) a = 1; } }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { let a, b; if (foo) { ({ a, b } = foo); } } }",
+ output: null,
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "useConst", data: { name: "a" }, type: "Identifier" },
+ { messageId: "useConst", data: { name: "b" }, type: "Identifier" }
+ ]
+ },
+ {
+ code: "class C { static { let a, b; if (foo) ({ a, b } = foo); } }",
+ output: null,
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "useConst", data: { name: "a" }, type: "Identifier" },
+ { messageId: "useConst", data: { name: "b" }, type: "Identifier" }
+ ]
+ },
+ {
+ code: "class C { static { a; } } let a = 1; ",
+ options: [{ ignoreReadBeforeAssign: true }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { () => a; let a = 1; } };",
+ options: [{ ignoreReadBeforeAssign: true }],
+ parserOptions: { ecmaVersion: 2022 }
+ }
],
invalid: [
{
code: "/*eslint no-undef-init:error*/ let foo = undefined;",
output: "/*eslint no-undef-init:error*/ const foo = undefined;",
errors: 2
+ },
+
+ {
+ code: "let a = 1; class C { static { a; } }",
+ output: "const a = 1; class C { static { a; } }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "useConst", data: { name: "a" }, type: "Identifier" }]
+ },
+ {
+
+ // this is a TDZ error with either `let` or `const`, but that isn't a concern of this rule
+ code: "class C { static { a; } } let a = 1;",
+ output: "class C { static { a; } } const a = 1;",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "useConst", data: { name: "a" }, type: "Identifier" }]
+ },
+ {
+ code: "class C { static { let a = 1; } }",
+ output: "class C { static { const a = 1; } }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "useConst", data: { name: "a" }, type: "Identifier" }]
+ },
+ {
+ code: "class C { static { if (foo) { let a = 1; } } }",
+ output: "class C { static { if (foo) { const a = 1; } } }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "useConst", data: { name: "a" }, type: "Identifier" }]
+ },
+ {
+ code: "class C { static { let a = 1; if (foo) { a; } } }",
+ output: "class C { static { const a = 1; if (foo) { a; } } }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "useConst", data: { name: "a" }, type: "Identifier" }]
+ },
+ {
+ code: "class C { static { if (foo) { let a; a = 1; } } }",
+ output: null,
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "useConst", data: { name: "a" }, type: "Identifier" }]
+ },
+ {
+ code: "class C { static { let a; a = 1; } }",
+ output: null,
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "useConst", data: { name: "a" }, type: "Identifier", column: 27 }]
+ },
+ {
+ code: "class C { static { let { a, b } = foo; } }",
+ output: "class C { static { const { a, b } = foo; } }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "useConst", data: { name: "a" }, type: "Identifier" },
+ { messageId: "useConst", data: { name: "b" }, type: "Identifier" }
+ ]
+ },
+ {
+ code: "class C { static { let a, b; ({ a, b } = foo); } }",
+ output: null,
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "useConst", data: { name: "a" }, type: "Identifier" },
+ { messageId: "useConst", data: { name: "b" }, type: "Identifier" }
+ ]
+ },
+ {
+ code: "class C { static { let a; let b; ({ a, b } = foo); } }",
+ output: null,
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "useConst", data: { name: "a" }, type: "Identifier" },
+ { messageId: "useConst", data: { name: "b" }, type: "Identifier" }
+ ]
+ },
+ {
+ code: "class C { static { let a; a = 0; console.log(a); } }",
+ output: null,
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "useConst", data: { name: "a" }, type: "Identifier" }
+ ]
}
]
});
// Tests
//------------------------------------------------------------------------------
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } });
ruleTester.run("prefer-destructuring", rule, {
valid: [
// Optional chaining
"var foo = array?.[0];", // because the fixed code can throw TypeError.
- "var foo = object?.foo;"
+ "var foo = object?.foo;",
+
+ // Private identifiers
+ "class C { #x; foo() { const x = this.#x; } }",
+ "class C { #x; foo() { x = this.#x; } }",
+ "class C { #x; foo(a) { x = a.#x; } }",
+ {
+ code: "class C { #x; foo() { const x = this.#x; } }",
+ options: [{ array: true, object: true }, { enforceForRenamedProperties: true }]
+ },
+ {
+ code: "class C { #x; foo() { const y = this.#x; } }",
+ options: [{ array: true, object: true }, { enforceForRenamedProperties: true }]
+ },
+ {
+ code: "class C { #x; foo() { x = this.#x; } }",
+ options: [{ array: true, object: true }, { enforceForRenamedProperties: true }]
+ },
+ {
+ code: "class C { #x; foo() { y = this.#x; } }",
+ options: [{ array: true, object: true }, { enforceForRenamedProperties: true }]
+ },
+ {
+ code: "class C { #x; foo(a) { x = a.#x; } }",
+ options: [{ array: true, object: true }, { enforceForRenamedProperties: true }]
+ },
+ {
+ code: "class C { #x; foo(a) { y = a.#x; } }",
+ options: [{ array: true, object: true }, { enforceForRenamedProperties: true }]
+ },
+ {
+ code: "class C { #x; foo() { x = this.a.#x; } }",
+ options: [{ array: true, object: true }, { enforceForRenamedProperties: true }]
+ }
],
invalid: [
// Tests
//------------------------------------------------------------------------------
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } });
ruleTester.run("prefer-exponentiation-operator", rule, {
valid: [
globalThis.Math.pow(a, b)
`,
env: { es2020: true }
- }
+ },
+
+ "class C { #pow; foo() { Math.#pow(a, b); } }"
],
invalid: [
{
code: "parseInt(1n, 2);",
parserOptions: { ecmaVersion: 2020 }
+ },
+ {
+ code: "class C { #parseInt; foo() { Number.#parseInt(\"111110111\", 2); } }",
+ parserOptions: { ecmaVersion: 2022 }
}
],
invalid: [
"use strict";
-const rule = require("../../../lib/rules/prefer-object-spread");
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+const rule = require("../../../lib/rules/prefer-object-spread");
const { RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const parserOptions = {
ecmaVersion: 2018,
sourceType: "module"
`,
env: { es2020: true }
},
+ {
+ code: "class C { #assign; foo() { Object.#assign({}, foo); } }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
// ignore Object.assign() with > 1 arguments if any of the arguments is an object expression with a getter/setter
"Object.assign({ get a() {} }, {})",
const rule = require("../../../lib/rules/prefer-promise-reject-errors");
const { RuleTester } = require("../../../lib/rule-tester");
-
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2021 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } });
ruleTester.run("prefer-promise-reject-errors", rule, {
"Promise.reject(foo = new Error())",
"Promise.reject(foo ||= 5)",
"Promise.reject(foo.bar ??= 5)",
- "Promise.reject(foo[bar] ??= 5)"
+ "Promise.reject(foo[bar] ??= 5)",
+
+ // Private fields
+ "class C { #reject; foo() { Promise.#reject(5); } }",
+ "class C { #error; foo() { Promise.reject(this.#error); } }"
],
invalid: [
// Tests
//------------------------------------------------------------------------------
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } });
ruleTester.run("prefer-regex-literals", rule, {
valid: [
{
code: "new globalThis.RegExp('a');",
env: { es2017: true }
+ },
+ {
+ code: "class C { #RegExp; foo() { globalThis.#RegExp('a'); } }",
+ env: { es2020: true }
}
],
const rule = require("../../../lib/rules/prefer-rest-params"),
{ RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
ruleTester.run("prefer-rest-params", rule, {
const errors = [{ messageId: "preferSpread", type: "CallExpression" }];
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } });
ruleTester.run("prefer-spread", rule, {
valid: [
// Optional chaining
"(a?.b).c.foo.apply(a?.b.c, args);",
- "a?.b.c.foo.apply((a?.b).c, args);"
+ "a?.b.c.foo.apply((a?.b).c, args);",
+
+ // Private fields
+ "class C { #apply; foo() { foo.#apply(undefined, args); } }"
],
invalid: [
{
{
code: "(a?.b).c.foo.apply((a?.b).c, args);",
errors
+ },
+
+ // Private fields
+ {
+ code: "class C { #foo; foo() { obj.#foo.apply(obj, args); } }",
+ errors
}
]
});
const rule = require("../../../lib/rules/quote-props"),
{ RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester();
ruleTester.run("quote-props", rule, {
const rule = require("../../../lib/rules/quotes"),
{ RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester();
ruleTester.run("quotes", rule, {
{ code: "var foo = \"a string containing `backtick` quotes\";", options: ["backtick", { avoidEscape: true }] },
{ code: "var foo = <div id=\"foo\"></div>;", options: ["backtick"], parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } },
{ code: "var foo = <div>Hello world</div>;", options: ["backtick"], parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } },
+ { code: "class C { \"f\"; \"m\"() {} }", options: ["double"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { 'f'; 'm'() {} }", options: ["single"], parserOptions: { ecmaVersion: 2022 } },
// Backticks are only okay if they have substitutions, contain a line break, or are tagged
{ code: "var foo = `back\ntick`;", options: ["single"], parserOptions: { ecmaVersion: 6 } },
// `backtick` should not warn property/method names (not computed).
{ code: "var obj = {\"key0\": 0, 'key1': 1};", options: ["backtick"], parserOptions: { ecmaVersion: 6 } },
{ code: "class Foo { 'bar'(){} }", options: ["backtick"], parserOptions: { ecmaVersion: 6 } },
- { code: "class Foo { static ''(){} }", options: ["backtick"], parserOptions: { ecmaVersion: 6 } }
+ { code: "class Foo { static ''(){} }", options: ["backtick"], parserOptions: { ecmaVersion: 6 } },
+ { code: "class C { \"double\"; 'single'; }", options: ["backtick"], parserOptions: { ecmaVersion: 2022 } }
],
invalid: [
{
type: "Literal"
}
]
+ },
+
+
+ // class members
+ {
+ code: "class C { 'foo'; }",
+ output: "class C { \"foo\"; }",
+ options: ["double"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ {
+ messageId: "wrongQuotes",
+ data: { description: "doublequote" },
+ type: "Literal"
+ }
+ ]
+ },
+ {
+ code: "class C { 'foo'() {} }",
+ output: "class C { \"foo\"() {} }",
+ options: ["double"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ {
+ messageId: "wrongQuotes",
+ data: { description: "doublequote" },
+ type: "Literal"
+ }
+ ]
+ },
+ {
+ code: "class C { \"foo\"; }",
+ output: "class C { 'foo'; }",
+ options: ["single"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ {
+ messageId: "wrongQuotes",
+ data: { description: "singlequote" },
+ type: "Literal"
+ }
+ ]
+ },
+ {
+ code: "class C { \"foo\"() {} }",
+ output: "class C { 'foo'() {} }",
+ options: ["single"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ {
+ messageId: "wrongQuotes",
+ data: { description: "singlequote" },
+ type: "Literal"
+ }
+ ]
+ },
+ {
+ code: "class C { [\"foo\"]; }",
+ output: "class C { [`foo`]; }",
+ options: ["backtick"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ {
+ messageId: "wrongQuotes",
+ data: { description: "backtick" },
+ type: "Literal"
+ }
+ ]
+ },
+ {
+ code: "class C { foo = \"foo\"; }",
+ output: "class C { foo = `foo`; }",
+ options: ["backtick"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ {
+ messageId: "wrongQuotes",
+ data: { description: "backtick" },
+ type: "Literal"
+ }
+ ]
}
]
});
const rule = require("../../../lib/rules/radix"),
{ RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester();
ruleTester.run("radix", rule, {
"parseInt",
"Number.foo();",
"Number[parseInt]();",
+ { code: "class C { #parseInt; foo() { Number.#parseInt(); } }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { #parseInt; foo() { Number.#parseInt(foo); } }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { #parseInt; foo() { Number.#parseInt(foo, 'bar'); } }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { #parseInt; foo() { Number.#parseInt(foo, 10); } }", options: ["as-needed"], parserOptions: { ecmaVersion: 2022 } },
// Ignores if it's shadowed or disabled.
"var parseInt; parseInt();",
const rule = require("../../../lib/rules/require-atomic-updates");
const { RuleTester } = require("../../../lib/rule-tester");
-
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } });
const VARIABLE_ERROR = {
messageId: "nonAtomicUpdate",
};
const STATIC_PROPERTY_ERROR = {
- messageId: "nonAtomicUpdate",
- data: { value: "foo.bar" },
+ messageId: "nonAtomicObjectUpdate",
+ data: { value: "foo.bar", object: "foo" },
type: "AssignmentExpression"
};
const COMPUTED_PROPERTY_ERROR = {
- messageId: "nonAtomicUpdate",
- data: { value: "foo[bar].baz" },
+ messageId: "nonAtomicObjectUpdate",
+ data: { value: "foo[bar].baz", object: "foo" },
+ type: "AssignmentExpression"
+};
+
+const PRIVATE_PROPERTY_ERROR = {
+ messageId: "nonAtomicObjectUpdate",
+ data: { value: "foo.#bar", object: "foo" },
type: "AssignmentExpression"
};
await a;
b = 1;
}
- `
+ `,
+
+ // allowProperties
+ {
+ code: `
+ async function a(foo) {
+ if (foo.bar) {
+ foo.bar = await something;
+ }
+ }
+ `,
+ options: [{ allowProperties: true }]
+ },
+ {
+ code: `
+ function* g(foo) {
+ baz = foo.bar;
+ yield something;
+ foo.bar = 1;
+ }
+ `,
+ options: [{ allowProperties: true }]
+ }
],
invalid: [
code: "const foo = []; async function x() { foo[bar].baz += await result; }",
errors: [COMPUTED_PROPERTY_ERROR]
},
+ {
+ code: "const foo = {}; class C { #bar; async wrap() { foo.#bar += await baz } }",
+ errors: [PRIVATE_PROPERTY_ERROR]
+ },
{
code: "let foo; async function* x() { foo = (yield foo) + await bar; }",
errors: [VARIABLE_ERROR]
}
`,
errors: [STATIC_PROPERTY_ERROR]
+ },
+
+ // https://github.com/eslint/eslint/issues/15076
+ {
+ code: `
+ async () => {
+ opts.spec = process.stdin;
+ try {
+ const { exit_code } = await run(opts);
+ process.exitCode = exit_code;
+ } catch (e) {
+ process.exitCode = 1;
+ }
+ };
+ `,
+ env: { node: true },
+ errors: [
+ {
+ messageId: "nonAtomicObjectUpdate",
+ data: { value: "process.exitCode", object: "process" },
+ type: "AssignmentExpression",
+ line: 6
+ },
+ {
+ messageId: "nonAtomicObjectUpdate",
+ data: { value: "process.exitCode", object: "process" },
+ type: "AssignmentExpression",
+ line: 8
+ }
+ ]
+ },
+
+ // allowProperties
+ {
+ code: `
+ async function a(foo) {
+ if (foo.bar) {
+ foo.bar = await something;
+ }
+ }
+ `,
+ errors: [STATIC_PROPERTY_ERROR]
+ },
+ {
+ code: `
+ function* g(foo) {
+ baz = foo.bar;
+ yield something;
+ foo.bar = 1;
+ }
+ `,
+ errors: [STATIC_PROPERTY_ERROR]
+ },
+ {
+ code: `
+ async function a(foo) {
+ if (foo.bar) {
+ foo.bar = await something;
+ }
+ }
+ `,
+ options: [{}],
+ errors: [STATIC_PROPERTY_ERROR]
+
+ },
+ {
+ code: `
+ function* g(foo) {
+ baz = foo.bar;
+ yield something;
+ foo.bar = 1;
+ }
+ `,
+ options: [{}],
+ errors: [STATIC_PROPERTY_ERROR]
+ },
+ {
+ code: `
+ async function a(foo) {
+ if (foo.bar) {
+ foo.bar = await something;
+ }
+ }
+ `,
+ options: [{ allowProperties: false }],
+ errors: [STATIC_PROPERTY_ERROR]
+
+ },
+ {
+ code: `
+ function* g(foo) {
+ baz = foo.bar;
+ yield something;
+ foo.bar = 1;
+ }
+ `,
+ options: [{ allowProperties: false }],
+ errors: [STATIC_PROPERTY_ERROR]
+ },
+ {
+ code: `
+ let foo;
+ async function a() {
+ if (foo) {
+ foo = await something;
+ }
+ }
+ `,
+ options: [{ allowProperties: true }],
+ errors: [VARIABLE_ERROR]
+
+ },
+ {
+ code: `
+ let foo;
+ function* g() {
+ baz = foo;
+ yield something;
+ foo = 1;
+ }
+ `,
+ options: [{ allowProperties: true }],
+ errors: [VARIABLE_ERROR]
}
]
});
*/
"use strict";
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
const rule = require("../../../lib/rules/require-jsdoc"),
{ RuleTester } = require("../../../lib/rule-tester");
{ code: "globalThis.RegExp('foo', 'u')", env: { es2020: true } },
{ code: "const flags = 'u'; new globalThis.RegExp('', flags)", env: { es2020: true } },
{ code: "const flags = 'g'; new globalThis.RegExp('', flags + 'u')", env: { es2020: true } },
- { code: "const flags = 'gimu'; new globalThis.RegExp('foo', flags[3])", env: { es2020: true } }
+ { code: "const flags = 'gimu'; new globalThis.RegExp('foo', flags[3])", env: { es2020: true } },
+ { code: "class C { #RegExp; foo() { new globalThis.#RegExp('foo') } }", parserOptions: { ecmaVersion: 2022 }, env: { es2020: true } }
],
invalid: [
{
const rule = require("../../../lib/rules/rest-spread-spacing"),
{ RuleTester } = require("../../../lib/rule-tester");
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
ruleTester.run("rest-spread-spacing", rule, {
{ code: "function foo() { return 2; }", options: [{ after: false }] },
{ code: "for ( var i = 0;i < results.length; ) {}", options: [{ after: false }] },
- "do {} while (true); foo"
+ "do {} while (true); foo",
+
+ // Class fields
+ {
+ code: "class C { foo; bar; method() {} }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { foo }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+
+ // Empty are ignored (`no-extra-semi` rule will remove those)
+ "foo; ;;;;;;;;;",
+ {
+ code: "class C { foo; ;;;;;;;;;; }",
+ parserOptions: { ecmaVersion: 2022 }
+ }
],
invalid: [
{
endLine: 1,
endColumn: 22
}]
+ },
+
+ // Class fields
+ {
+ code: "class C { foo ;bar;}",
+ output: "class C { foo; bar;}",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ {
+ messageId: "unexpectedWhitespaceBefore",
+ type: "PropertyDefinition",
+ line: 1,
+ column: 14,
+ endLine: 1,
+ endColumn: 15
+ },
+ {
+ messageId: "missingWhitespaceAfter",
+ type: "PropertyDefinition",
+ line: 1,
+ column: 15,
+ endLine: 1,
+ endColumn: 16
+ }
+ ]
+ },
+ {
+ code: "class C { foo; bar ; }",
+ output: "class C { foo ;bar ; }",
+ options: [{ before: true, after: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ {
+ messageId: "missingWhitespaceBefore",
+ type: "PropertyDefinition",
+ line: 1,
+ column: 14,
+ endLine: 1,
+ endColumn: 15
+ },
+ {
+ messageId: "unexpectedWhitespaceAfter",
+ type: "PropertyDefinition",
+ line: 1,
+ column: 15,
+ endLine: 1,
+ endColumn: 16
+ }
+ ]
+ },
+ {
+ code: "class C { foo;static {}}",
+ output: "class C { foo; static {}}",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "missingWhitespaceAfter",
+ type: "PropertyDefinition",
+ line: 1,
+ column: 14,
+ endLine: 1,
+ endColumn: 15
+ }]
}
]
});
{ code: "for(a;b;c);", options: ["last"] },
{ code: "for(a;\nb;\nc);", options: ["last"] },
{ code: "for((a\n);\n(b\n);\n(c));", options: ["last"] },
+ { code: "class C { a; b; }", options: ["last"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C {\na;\nb;\n}", options: ["last"], parserOptions: { ecmaVersion: 2022 } },
{ code: "if(a)foo;\nbar", options: ["last"] },
{ code: ";", options: ["first"] },
{ code: ";foo;bar;baz;", options: ["first"] },
{ code: "for(a;b;c);", options: ["first"] },
{ code: "for(a;\nb;\nc);", options: ["first"] },
{ code: "for((a\n);\n(b\n);\n(c));", options: ["first"] },
+ { code: "class C { a ;b }", options: ["first"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C {\na\n;b\n}", options: ["first"], parserOptions: { ecmaVersion: 2022 } },
// edge cases
{
while (a)
`,
options: ["last"]
+ },
+
+ // Class static blocks
+ {
+ code: `
+ class C {
+ static {}
+ }
+ `,
+ options: ["last"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: `
+ class C {
+ static {
+ foo
+ }
+ }
+ `,
+ options: ["last"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: `
+ class C {
+ static {
+ foo
+ bar
+ }
+ }
+ `,
+ options: ["last"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: `
+ class C {
+ static {
+ ;
+ }
+ }
+ `,
+ options: ["last"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: `
+ class C {
+ static {
+ foo;
+ }
+ }
+ `,
+ options: ["last"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: `
+ class C {
+ static {
+ foo;
+ bar;
+ }
+ }
+ `,
+ options: ["last"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: `
+ class C {
+ static {
+ foo;
+ bar;
+ baz;
+ }
+ }
+ `,
+ options: ["last"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: `
+ class C {
+ static {}
+ }
+ `,
+ options: ["first"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: `
+ class C {
+ static {
+ foo
+ }
+ }
+ `,
+ options: ["first"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: `
+ class C {
+ static {
+ foo
+ bar
+ }
+ }
+ `,
+ options: ["first"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: `
+ class C {
+ static {
+ ;
+ }
+ }
+ `,
+ options: ["first"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: `
+ class C {
+ static {
+ ;foo
+ }
+ }
+ `,
+ options: ["first"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: `
+ class C {
+ static {
+ foo;
+ }
+ }
+ `,
+ options: ["first"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: `
+ class C {
+ static {
+ foo
+ ;bar
+ }
+ }
+ `,
+ options: ["first"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: `
+ class C {
+ static {
+ foo
+ ;bar;
+ }
+ }
+ `,
+ options: ["first"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: `
+ class C {
+ static {
+ foo
+ ;bar
+ ;baz
+ }
+ }
+ `,
+ options: ["first"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: `
+ class C {
+ static {
+ foo
+ ;bar
+ ;baz;
+ }
+ }
+ `,
+ options: ["first"],
+ parserOptions: { ecmaVersion: 2022 }
}
],
invalid: [
pos: "the beginning of the next line"
}
}]
+ },
+
+ // Class fields
+ {
+ code: "class C { foo\n;bar }",
+ output: "class C { foo;\nbar }",
+ options: ["last"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "expectedSemiColon",
+ data: {
+ pos: "the end of the previous line"
+ }
+ }]
+ },
+ {
+ code: "class C { foo;\nbar }",
+ output: "class C { foo\n;bar }",
+ options: ["first"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "expectedSemiColon",
+ data: {
+ pos: "the beginning of the next line"
+ }
+ }]
+ },
+
+ // Class static blocks
+ {
+ code: "class C { static { foo\n; } }",
+ output: "class C { static { foo;\n} }",
+ options: ["last"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "expectedSemiColon",
+ data: {
+ pos: "the end of the previous line"
+ }
+ }]
+ },
+ {
+ code: "class C { static { foo\n ;bar } }",
+ output: "class C { static { foo;\nbar } }",
+ options: ["last"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "expectedSemiColon",
+ data: {
+ pos: "the end of the previous line"
+ }
+ }]
+ },
+ {
+ code: "class C { static { foo;\nbar\n ; } }",
+ output: "class C { static { foo;\nbar;\n} }",
+ options: ["last"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "expectedSemiColon",
+ data: {
+ pos: "the end of the previous line"
+ }
+ }]
+ },
+ {
+ code: "class C { static { foo;\nbar } }",
+ output: "class C { static { foo\n;bar } }",
+ options: ["first"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "expectedSemiColon",
+ data: {
+ pos: "the beginning of the next line"
+ }
+ }]
}
]
});
const rule = require("../../../lib/rules/semi"),
{ RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester();
ruleTester.run("semi", rule, {
{ code: "for (let thing of {}) {\n console.log(thing);\n}", parserOptions: { ecmaVersion: 6 } },
{ code: "do{}while(true)", options: ["never"] },
{ code: "do{}while(true);", options: ["always"] },
+ { code: "class C { static {} }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static {} }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { foo(); } }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { foo(); } }", options: ["always"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { foo(); bar(); } }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { foo(); bar(); baz();} }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { foo() } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { foo()\nbar() } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { foo()\nbar()\nbaz() } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { foo(); bar() } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { foo();\n (a) } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { foo()\n ;(a) } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { foo();\n [a] } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { foo()\n ;[a] } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { foo();\n +a } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { foo()\n ;+a } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { foo();\n -a } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { foo()\n ;-a } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { foo();\n /a/ } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { foo()\n ;/a/} }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+ {
+ code: "class C { static { foo();\n (a) } }",
+ options: ["never", { beforeStatementContinuationChars: "never" }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { do ; while (foo)\n (a)} }",
+ options: ["never", { beforeStatementContinuationChars: "never" }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static { do ; while (foo)\n ;(a)} }",
+ options: ["never", { beforeStatementContinuationChars: "always" }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ // omitLastInOneLineBlock: true
{ code: "if (foo) { bar() }", options: ["always", { omitLastInOneLineBlock: true }] },
{ code: "if (foo) { bar(); baz() }", options: ["always", { omitLastInOneLineBlock: true }] },
+ { code: "if (foo)\n{ bar(); baz() }", options: ["always", { omitLastInOneLineBlock: true }] },
+ { code: "if (foo) {\n bar(); baz(); }", options: ["always", { omitLastInOneLineBlock: true }] },
+ { code: "if (foo) { bar(); baz(); \n}", options: ["always", { omitLastInOneLineBlock: true }] },
+ { code: "function foo() { bar(); baz() }", options: ["always", { omitLastInOneLineBlock: true }] },
+ { code: "function foo()\n{ bar(); baz() }", options: ["always", { omitLastInOneLineBlock: true }] },
+ { code: "function foo(){\n bar(); baz(); }", options: ["always", { omitLastInOneLineBlock: true }] },
+ { code: "function foo(){ bar(); baz(); \n}", options: ["always", { omitLastInOneLineBlock: true }] },
+ { code: "() => { bar(); baz() };", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } },
+ { code: "() =>\n { bar(); baz() };", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } },
+ { code: "() => {\n bar(); baz(); };", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } },
+ { code: "() => { bar(); baz(); \n};", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } },
+ { code: "const obj = { method() { bar(); baz() } };", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } },
+ { code: "const obj = { method()\n { bar(); baz() } };", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } },
+ { code: "const obj = { method() {\n bar(); baz(); } };", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } },
+ { code: "const obj = { method() { bar(); baz(); \n} };", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } },
+ { code: "class C {\n method() { bar(); baz() } \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } },
+ { code: "class C {\n method()\n { bar(); baz() } \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } },
+ { code: "class C {\n method() {\n bar(); baz(); } \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } },
+ { code: "class C {\n method() { bar(); baz(); \n} \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } },
+ { code: "class C {\n static { bar(); baz() } \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C {\n static\n { bar(); baz() } \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C {\n static {\n bar(); baz(); } \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C {\n static { bar(); baz(); \n} \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 2022 } },
-
- // method definitions don't have a semicolon.
+ // method definitions and static blocks don't have a semicolon.
{ code: "class A { a() {} b() {} }", parserOptions: { ecmaVersion: 6 } },
{ code: "var A = class { a() {} b() {} };", parserOptions: { ecmaVersion: 6 } },
+ { code: "class A { static {} }", parserOptions: { ecmaVersion: 2022 } },
{ code: "import theDefault, { named1, named2 } from 'src/mylib';", parserOptions: { ecmaVersion: 6, sourceType: "module" } },
{ code: "import theDefault, { named1, named2 } from 'src/mylib'", options: ["never"], parserOptions: { ecmaVersion: 6, sourceType: "module" } },
`,
options: ["never", { beforeStatementContinuationChars: "never" }],
parserOptions: { ecmaVersion: 2015 }
+ },
+
+ // Class fields
+ {
+ code: "class C { foo; }",
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { foo; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { foo }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { foo = obj\n;[bar] }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { foo;\n[bar]; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { foo\n;[bar] }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { foo\n[bar] }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { foo\n;[bar] }",
+ options: ["never", { beforeStatementContinuationChars: "always" }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { foo\n[bar] }",
+ options: ["never", { beforeStatementContinuationChars: "never" }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { foo = () => {}\n;[bar] }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { foo = () => {}\n[bar] }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { foo = () => {}\n;[bar] }",
+ options: ["never", { beforeStatementContinuationChars: "always" }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { foo = () => {}\n[bar] }",
+ options: ["never", { beforeStatementContinuationChars: "never" }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { foo() {} }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { foo() {}; }", // no-extra-semi reports it
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static {}; }", // no-extra-semi reports it
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { a=b;\n*foo() {} }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { get;\nfoo() {} }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { set;\nfoo() {} }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static;\nfoo() {} }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { a=b;\nin }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { a=b;\ninstanceof }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: `
+ class C {
+ x
+ [foo]
+
+ x;
+ [foo]
+
+ x = "a";
+ [foo]
+ }
+ `,
+ options: ["never", { beforeStatementContinuationChars: "never" }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: `
+ class C {
+ x
+ [foo]
+
+ x;
+ [foo]
+
+ x = 1;
+ [foo]
+ }
+ `,
+ options: ["never", { beforeStatementContinuationChars: "always" }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { foo\n[bar] }",
+ options: ["never", { beforeStatementContinuationChars: "always" }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { foo = () => {}\n[bar] }",
+ options: ["never", { beforeStatementContinuationChars: "always" }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { foo\n;[bar] }",
+ options: ["never", { beforeStatementContinuationChars: "never" }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { foo = () => {}\n;[bar] }",
+ options: ["never", { beforeStatementContinuationChars: "never" }],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { [foo] = bar;\nin }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { #foo = bar;\nin }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static static = bar;\nin }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { [foo];\nin }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { [get];\nin }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { [get] = 5;\nin }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { #get;\nin }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { #set = 5;\nin }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "class C { static static;\nin }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
}
],
invalid: [
errors: [{
messageId: "missingSemi",
type: "ImportDeclaration",
+ line: 1,
column: 33,
endLine: void 0,
endColumn: void 0
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
messageId: "missingSemi",
- type: "ImportDeclaration"
+ type: "ImportDeclaration",
+ line: 1,
+ column: 35,
+ endLine: void 0,
+ endColumn: void 0
}]
},
{
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
messageId: "missingSemi",
- type: "ImportDeclaration"
+ type: "ImportDeclaration",
+ line: 1,
+ column: 37,
+ endLine: void 0,
+ endColumn: void 0
}]
},
{
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
messageId: "missingSemi",
- type: "ImportDeclaration"
+ type: "ImportDeclaration",
+ line: 1,
+ column: 19,
+ endLine: void 0,
+ endColumn: void 0
}]
},
{
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
messageId: "missingSemi",
- type: "ImportDeclaration"
+ type: "ImportDeclaration",
+ line: 1,
+ column: 55,
+ endLine: void 0,
+ endColumn: void 0
}]
},
{
output: "function foo() { return []; }",
errors: [{
messageId: "missingSemi",
- type: "ReturnStatement"
+ type: "ReturnStatement",
+ line: 1,
+ column: 27,
+ endLine: 1,
+ endColumn: 28
}]
},
{
output: "while(true) { break; }",
errors: [{
messageId: "missingSemi",
- type: "BreakStatement"
+ type: "BreakStatement",
+ line: 1,
+ column: 20,
+ endLine: 1,
+ endColumn: 21
}]
},
{
output: "while(true) { continue; }",
errors: [{
messageId: "missingSemi",
- type: "ContinueStatement"
+ type: "ContinueStatement",
+ line: 1,
+ column: 23,
+ endLine: 1,
+ endColumn: 24
}]
},
{
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "missingSemi",
- type: "VariableDeclaration"
+ type: "VariableDeclaration",
+ line: 1,
+ column: 10,
+ endLine: void 0,
+ endColumn: void 0
}]
},
{
output: "var x = 5;",
errors: [{
messageId: "missingSemi",
- type: "VariableDeclaration"
+ type: "VariableDeclaration",
+ line: 1,
+ column: 10,
+ endLine: void 0,
+ endColumn: void 0
}]
},
{
output: "var x = 5, y;",
errors: [{
messageId: "missingSemi",
- type: "VariableDeclaration"
+ type: "VariableDeclaration",
+ line: 1,
+ column: 13,
+ endLine: void 0,
+ endColumn: void 0
}]
},
{
output: "debugger;",
errors: [{
messageId: "missingSemi",
- type: "DebuggerStatement"
+ type: "DebuggerStatement",
+ line: 1,
+ column: 9,
+ endLine: void 0,
+ endColumn: void 0
}]
},
{
errors: [{
messageId: "missingSemi",
type: "ExpressionStatement",
+ line: 1,
column: 6,
+ endLine: void 0,
endColumn: void 0
}]
},
errors: [{
messageId: "missingSemi",
type: "ExpressionStatement",
+ line: 1,
column: 6,
endLine: 2,
endColumn: 1
errors: [{
messageId: "missingSemi",
type: "ExpressionStatement",
+ line: 1,
column: 6,
endLine: 2,
endColumn: 1
errors: [{
messageId: "missingSemi",
type: "ExpressionStatement",
+ line: 1,
column: 6,
endLine: 2,
endColumn: 1
errors: [{
messageId: "missingSemi",
type: "ExpressionStatement",
+ line: 1,
column: 6,
endLine: 2,
endColumn: 1
output: "for (var a in b) var i; ",
errors: [{
messageId: "missingSemi",
- type: "VariableDeclaration"
+ type: "VariableDeclaration",
+ line: 1,
+ column: 23,
+ endLine: 1,
+ endColumn: 24
}]
},
{
output: "for (;;){var i;}",
errors: [{
messageId: "missingSemi",
- type: "VariableDeclaration"
+ type: "VariableDeclaration",
+ line: 1,
+ column: 15,
+ endLine: 1,
+ endColumn: 16
}]
},
{
output: "for (;;) var i; ",
errors: [{
messageId: "missingSemi",
- type: "VariableDeclaration"
+ type: "VariableDeclaration",
+ line: 1,
+ column: 15,
+ endLine: 1,
+ endColumn: 16
}]
},
{
output: "for (var j;;) {var i;}",
errors: [{
messageId: "missingSemi",
- type: "VariableDeclaration"
+ type: "VariableDeclaration",
+ line: 1,
+ column: 21,
+ endLine: 1,
+ endColumn: 22
}]
},
{
errors: [{
messageId: "missingSemi",
type: "VariableDeclaration",
- line: 3
+ line: 3,
+ column: 2,
+ endLine: void 0,
+ endColumn: void 0
}]
},
{
errors: [{
messageId: "missingSemi",
type: "VariableDeclaration",
- line: 1
+ line: 1,
+ column: 8,
+ endLine: 2,
+ endColumn: 1
}]
},
{
errors: [{
messageId: "missingSemi",
type: "ThrowStatement",
- line: 1
+ line: 1,
+ column: 23,
+ endLine: void 0,
+ endColumn: void 0
}]
},
{
errors: [{
messageId: "missingSemi",
type: "DoWhileStatement",
- line: 1
+ line: 1,
+ column: 16,
+ endLine: void 0,
+ endColumn: void 0
}]
},
{
output: "if (foo) {bar();}",
errors: [{
messageId: "missingSemi",
+ line: 1,
column: 16,
+ endLine: 1,
endColumn: 17
}]
},
output: "if (foo) {bar();} ",
errors: [{
messageId: "missingSemi",
+ line: 1,
column: 16,
+ endLine: 1,
endColumn: 17
}]
},
output: "if (foo) {bar();\n}",
errors: [{
messageId: "missingSemi",
+ line: 1,
column: 16,
endLine: 2,
endColumn: 1
errors: [{
messageId: "extraSemi",
type: "ThrowStatement",
- column: 23
+ line: 1,
+ column: 23,
+ endLine: 1,
+ endColumn: 24
}]
},
{
options: ["never"],
errors: [{
messageId: "extraSemi",
- type: "ReturnStatement"
+ type: "ReturnStatement",
+ line: 1,
+ column: 27,
+ endLine: 1,
+ endColumn: 28
}]
},
{
options: ["never"],
errors: [{
messageId: "extraSemi",
- type: "BreakStatement"
+ type: "BreakStatement",
+ line: 1,
+ column: 20,
+ endLine: 1,
+ endColumn: 21
}]
},
{
options: ["never"],
errors: [{
messageId: "extraSemi",
- type: "ContinueStatement"
+ type: "ContinueStatement",
+ line: 1,
+ column: 23,
+ endLine: 1,
+ endColumn: 24
}]
},
{
parserOptions: { ecmaVersion: 6 },
errors: [{
messageId: "extraSemi",
- type: "VariableDeclaration"
+ type: "VariableDeclaration",
+ line: 1,
+ column: 10,
+ endLine: 1,
+ endColumn: 11
}]
},
{
options: ["never"],
errors: [{
messageId: "extraSemi",
- type: "VariableDeclaration"
+ type: "VariableDeclaration",
+ line: 1,
+ column: 10,
+ endLine: 1,
+ endColumn: 11
}]
},
{
options: ["never"],
errors: [{
messageId: "extraSemi",
- type: "VariableDeclaration"
+ type: "VariableDeclaration",
+ line: 1,
+ column: 13,
+ endLine: 1,
+ endColumn: 14
}]
},
{
options: ["never"],
errors: [{
messageId: "extraSemi",
- type: "DebuggerStatement"
+ type: "DebuggerStatement",
+ line: 1,
+ column: 9,
+ endLine: 1,
+ endColumn: 10
}]
},
{
options: ["never"],
errors: [{
messageId: "extraSemi",
- type: "ExpressionStatement"
+ type: "ExpressionStatement",
+ line: 1,
+ column: 6,
+ endLine: 1,
+ endColumn: 7
}]
},
{
options: ["never"],
errors: [{
messageId: "extraSemi",
- type: "VariableDeclaration"
+ type: "VariableDeclaration",
+ line: 1,
+ column: 23,
+ endLine: 1,
+ endColumn: 24
}]
},
{
options: ["never"],
errors: [{
messageId: "extraSemi",
- type: "VariableDeclaration"
+ type: "VariableDeclaration",
+ line: 1,
+ column: 15,
+ endLine: 1,
+ endColumn: 16
}]
},
{
options: ["never"],
errors: [{
messageId: "extraSemi",
- type: "VariableDeclaration"
+ type: "VariableDeclaration",
+ line: 1,
+ column: 15,
+ endLine: 1,
+ endColumn: 16
}]
},
{
options: ["never"],
errors: [{
messageId: "extraSemi",
- type: "VariableDeclaration"
+ type: "VariableDeclaration",
+ line: 1,
+ column: 21,
+ endLine: 1,
+ endColumn: 22
}]
},
{
errors: [{
messageId: "extraSemi",
type: "VariableDeclaration",
- line: 3
+ line: 3,
+ column: 2,
+ endLine: 3,
+ endColumn: 3
}]
},
{
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
messageId: "extraSemi",
- type: "ImportDeclaration"
+ type: "ImportDeclaration",
+ line: 1,
+ column: 55,
+ endLine: 1,
+ endColumn: 56
}]
},
{
errors: [{
messageId: "extraSemi",
type: "DoWhileStatement",
- line: 1
+ line: 1,
+ column: 16,
+ endLine: 1,
+ endColumn: 17
}]
},
-
{
- code: "if (foo) { bar()\n }",
- output: "if (foo) { bar();\n }",
- options: ["always", { omitLastInOneLineBlock: true }],
+ code: "class C { static { foo() } }",
+ output: "class C { static { foo(); } }",
+ parserOptions: { ecmaVersion: 2022 },
errors: [{
- messageId: "missingSemi"
+ messageId: "missingSemi",
+ type: "ExpressionStatement",
+ line: 1,
+ column: 25,
+ endLine: 1,
+ endColumn: 26
}]
},
{
- code: "if (foo) {\n bar() }",
- output: "if (foo) {\n bar(); }",
- options: ["always", { omitLastInOneLineBlock: true }],
+ code: "class C { static { foo() } }",
+ output: "class C { static { foo(); } }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 },
errors: [{
- messageId: "missingSemi"
+ messageId: "missingSemi",
+ type: "ExpressionStatement",
+ line: 1,
+ column: 25,
+ endLine: 1,
+ endColumn: 26
}]
},
{
- code: "if (foo) {\n bar(); baz() }",
- output: "if (foo) {\n bar(); baz(); }",
- options: ["always", { omitLastInOneLineBlock: true }],
+ code: "class C { static { foo(); bar() } }",
+ output: "class C { static { foo(); bar(); } }",
+ parserOptions: { ecmaVersion: 2022 },
errors: [{
- messageId: "missingSemi"
+ messageId: "missingSemi",
+ type: "ExpressionStatement",
+ line: 1,
+ column: 32,
+ endLine: 1,
+ endColumn: 33
}]
},
{
- code: "if (foo) { bar(); }",
- output: "if (foo) { bar() }",
- options: ["always", { omitLastInOneLineBlock: true }],
+ code: "class C { static { foo()\nbar(); } }",
+ output: "class C { static { foo();\nbar(); } }",
+ parserOptions: { ecmaVersion: 2022 },
errors: [{
- messageId: "extraSemi"
+ messageId: "missingSemi",
+ type: "ExpressionStatement",
+ line: 1,
+ column: 25,
+ endLine: 2,
+ endColumn: 1
}]
},
-
-
- // exports, "always"
{
- code: "export * from 'foo'",
- output: "export * from 'foo';",
- parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ code: "class C { static { foo(); bar()\nbaz(); } }",
+ output: "class C { static { foo(); bar();\nbaz(); } }",
+ parserOptions: { ecmaVersion: 2022 },
errors: [{
messageId: "missingSemi",
- type: "ExportAllDeclaration"
+ type: "ExpressionStatement",
+ line: 1,
+ column: 32,
+ endLine: 2,
+ endColumn: 1
}]
},
{
- code: "export { foo } from 'foo'",
- output: "export { foo } from 'foo';",
- parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ code: "class C { static { foo(); } }",
+ output: "class C { static { foo() } }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
errors: [{
- messageId: "missingSemi",
- type: "ExportNamedDeclaration"
+ messageId: "extraSemi",
+ type: "ExpressionStatement",
+ line: 1,
+ column: 25,
+ endLine: 1,
+ endColumn: 26
}]
},
{
- code: "var foo = 0;export { foo }",
- output: "var foo = 0;export { foo };",
- parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ code: "class C { static { foo();\nbar() } }",
+ output: "class C { static { foo()\nbar() } }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
errors: [{
- messageId: "missingSemi",
- type: "ExportNamedDeclaration"
+ messageId: "extraSemi",
+ type: "ExpressionStatement",
+ line: 1,
+ column: 25,
+ endLine: 1,
+ endColumn: 26
}]
},
{
- code: "export var foo",
- output: "export var foo;",
- parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ code: "class C { static { foo()\nbar(); } }",
+ output: "class C { static { foo()\nbar() } }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
errors: [{
- messageId: "missingSemi",
- type: "VariableDeclaration"
+ messageId: "extraSemi",
+ type: "ExpressionStatement",
+ line: 2,
+ column: 6,
+ endLine: 2,
+ endColumn: 7
}]
},
{
- code: "export let foo",
- output: "export let foo;",
- parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ code: "class C { static { foo()\nbar();\nbaz() } }",
+ output: "class C { static { foo()\nbar()\nbaz() } }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
errors: [{
- messageId: "missingSemi",
- type: "VariableDeclaration"
+ messageId: "extraSemi",
+ type: "ExpressionStatement",
+ line: 2,
+ column: 6,
+ endLine: 2,
+ endColumn: 7
}]
},
{
- code: "export const FOO = 42",
- output: "export const FOO = 42;",
- parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ code: "class C { static { do ; while (foo)\n (a)} }",
+ output: "class C { static { do ; while (foo);\n (a)} }",
+ options: ["never", { beforeStatementContinuationChars: "always" }],
+ parserOptions: { ecmaVersion: 2022 },
errors: [{
messageId: "missingSemi",
- type: "VariableDeclaration"
- }]
- },
+ type: "DoWhileStatement",
+ line: 1,
+ column: 36,
+ endLine: 2,
+ endColumn: 1
+ }]
+ },
+ {
+ code: "class C { static { do ; while (foo)\n ;(a)} }",
+ output: "class C { static { do ; while (foo)\n (a)} }",
+ options: ["never", { beforeStatementContinuationChars: "never" }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "extraSemi",
+ type: "DoWhileStatement",
+ line: 2,
+ column: 2,
+ endLine: 2,
+ endColumn: 3
+ }]
+ },
+
+ // omitLastInOneLineBlock: true
+ {
+ code: "if (foo) { bar()\n }",
+ output: "if (foo) { bar();\n }",
+ options: ["always", { omitLastInOneLineBlock: true }],
+ errors: [{
+ messageId: "missingSemi",
+ line: 1,
+ column: 17,
+ endLine: 2,
+ endColumn: 1
+ }]
+ },
+ {
+ code: "if (foo) {\n bar() }",
+ output: "if (foo) {\n bar(); }",
+ options: ["always", { omitLastInOneLineBlock: true }],
+ errors: [{
+ messageId: "missingSemi",
+ line: 2,
+ column: 7,
+ endLine: 2,
+ endColumn: 8
+ }]
+ },
+ {
+ code: "if (foo) {\n bar(); baz() }",
+ output: "if (foo) {\n bar(); baz(); }",
+ options: ["always", { omitLastInOneLineBlock: true }],
+ errors: [{
+ messageId: "missingSemi",
+ line: 2,
+ column: 14,
+ endLine: 2,
+ endColumn: 15
+ }]
+ },
+ {
+ code: "if (foo) { bar(); }",
+ output: "if (foo) { bar() }",
+ options: ["always", { omitLastInOneLineBlock: true }],
+ errors: [{
+ messageId: "extraSemi",
+ line: 1,
+ column: 17,
+ endLine: 1,
+ endColumn: 18
+ }]
+ },
+ {
+ code: "function foo() { bar(); baz(); }",
+ output: "function foo() { bar(); baz() }",
+ options: ["always", { omitLastInOneLineBlock: true }],
+ errors: [{
+ messageId: "extraSemi",
+ line: 1,
+ column: 30,
+ endLine: 1,
+ endColumn: 31
+ }]
+ },
+ {
+ code: "function foo()\n{ bar(); baz(); }",
+ output: "function foo()\n{ bar(); baz() }",
+ options: ["always", { omitLastInOneLineBlock: true }],
+ errors: [{
+ messageId: "extraSemi",
+ line: 2,
+ column: 15,
+ endLine: 2,
+ endColumn: 16
+ }]
+ },
+ {
+ code: "function foo() {\n bar(); baz() }",
+ output: "function foo() {\n bar(); baz(); }",
+ options: ["always", { omitLastInOneLineBlock: true }],
+ errors: [{
+ messageId: "missingSemi",
+ line: 2,
+ column: 14,
+ endLine: 2,
+ endColumn: 15
+ }]
+ },
+ {
+ code: "function foo() { bar(); baz() \n}",
+ output: "function foo() { bar(); baz(); \n}",
+ options: ["always", { omitLastInOneLineBlock: true }],
+ errors: [{
+ messageId: "missingSemi",
+ line: 1,
+ column: 30,
+ endLine: 1,
+ endColumn: 31
+ }]
+ },
+ {
+ code: "class C {\nfoo() { bar(); baz(); }\n}",
+ output: "class C {\nfoo() { bar(); baz() }\n}",
+ options: ["always", { omitLastInOneLineBlock: true }],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [{
+ messageId: "extraSemi",
+ line: 2,
+ column: 21,
+ endLine: 2,
+ endColumn: 22
+ }]
+ },
+ {
+ code: "class C {\nfoo() \n{ bar(); baz(); }\n}",
+ output: "class C {\nfoo() \n{ bar(); baz() }\n}",
+ options: ["always", { omitLastInOneLineBlock: true }],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [{
+ messageId: "extraSemi",
+ line: 3,
+ column: 15,
+ endLine: 3,
+ endColumn: 16
+ }]
+ },
+ {
+ code: "class C {\nfoo() {\n bar(); baz() }\n}",
+ output: "class C {\nfoo() {\n bar(); baz(); }\n}",
+ options: ["always", { omitLastInOneLineBlock: true }],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [{
+ messageId: "missingSemi",
+ line: 3,
+ column: 14,
+ endLine: 3,
+ endColumn: 15
+ }]
+ },
+ {
+ code: "class C {\nfoo() { bar(); baz() \n}\n}",
+ output: "class C {\nfoo() { bar(); baz(); \n}\n}",
+ options: ["always", { omitLastInOneLineBlock: true }],
+ parserOptions: { ecmaVersion: 6 },
+ errors: [{
+ messageId: "missingSemi",
+ line: 2,
+ column: 21,
+ endLine: 2,
+ endColumn: 22
+ }]
+ },
+ {
+ code: "class C {\nstatic { bar(); baz(); }\n}",
+ output: "class C {\nstatic { bar(); baz() }\n}",
+ options: ["always", { omitLastInOneLineBlock: true }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "extraSemi",
+ line: 2,
+ column: 22,
+ endLine: 2,
+ endColumn: 23
+ }]
+ },
+ {
+ code: "class C {\nstatic \n{ bar(); baz(); }\n}",
+ output: "class C {\nstatic \n{ bar(); baz() }\n}",
+ options: ["always", { omitLastInOneLineBlock: true }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "extraSemi",
+ line: 3,
+ column: 15,
+ endLine: 3,
+ endColumn: 16
+ }]
+ },
+ {
+ code: "class C {\nstatic {\n bar(); baz() }\n}",
+ output: "class C {\nstatic {\n bar(); baz(); }\n}",
+ options: ["always", { omitLastInOneLineBlock: true }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "missingSemi",
+ line: 3,
+ column: 14,
+ endLine: 3,
+ endColumn: 15
+ }]
+ },
+ {
+ code: "class C {\nfoo() { bar(); baz() \n}\n}",
+ output: "class C {\nfoo() { bar(); baz(); \n}\n}",
+ options: ["always", { omitLastInOneLineBlock: true }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "missingSemi",
+ line: 2,
+ column: 21,
+ endLine: 2,
+ endColumn: 22
+ }]
+ },
+
+
+ // exports, "always"
+ {
+ code: "export * from 'foo'",
+ output: "export * from 'foo';",
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [{
+ messageId: "missingSemi",
+ type: "ExportAllDeclaration",
+ line: 1,
+ column: 20,
+ endLine: void 0,
+ endColumn: void 0
+ }]
+ },
+ {
+ code: "export { foo } from 'foo'",
+ output: "export { foo } from 'foo';",
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [{
+ messageId: "missingSemi",
+ type: "ExportNamedDeclaration",
+ line: 1,
+ column: 26,
+ endLine: void 0,
+ endColumn: void 0
+ }]
+ },
+ {
+ code: "var foo = 0;export { foo }",
+ output: "var foo = 0;export { foo };",
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [{
+ messageId: "missingSemi",
+ type: "ExportNamedDeclaration",
+ line: 1,
+ column: 27,
+ endLine: void 0,
+ endColumn: void 0
+ }]
+ },
+ {
+ code: "export var foo",
+ output: "export var foo;",
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [{
+ messageId: "missingSemi",
+ type: "VariableDeclaration",
+ line: 1,
+ column: 15,
+ endLine: void 0,
+ endColumn: void 0
+ }]
+ },
+ {
+ code: "export let foo",
+ output: "export let foo;",
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [{
+ messageId: "missingSemi",
+ type: "VariableDeclaration",
+ line: 1,
+ column: 15,
+ endLine: void 0,
+ endColumn: void 0
+ }]
+ },
+ {
+ code: "export const FOO = 42",
+ output: "export const FOO = 42;",
+ parserOptions: { ecmaVersion: 6, sourceType: "module" },
+ errors: [{
+ messageId: "missingSemi",
+ type: "VariableDeclaration",
+ line: 1,
+ column: 22,
+ endLine: void 0,
+ endColumn: void 0
+ }]
+ },
{
code: "export default foo || bar",
output: "export default foo || bar;",
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
messageId: "missingSemi",
- type: "ExportDefaultDeclaration"
+ type: "ExportDefaultDeclaration",
+ line: 1,
+ column: 26,
+ endLine: void 0,
+ endColumn: void 0
}]
},
{
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
messageId: "missingSemi",
- type: "ExportDefaultDeclaration"
+ type: "ExportDefaultDeclaration",
+ line: 1,
+ column: 34,
+ endLine: void 0,
+ endColumn: void 0
}]
},
{
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
messageId: "missingSemi",
- type: "ExportDefaultDeclaration"
+ type: "ExportDefaultDeclaration",
+ line: 1,
+ column: 24,
+ endLine: void 0,
+ endColumn: void 0
}]
},
{
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
messageId: "missingSemi",
- type: "ExportDefaultDeclaration"
+ type: "ExportDefaultDeclaration",
+ line: 1,
+ column: 25,
+ endLine: void 0,
+ endColumn: void 0
}]
},
errors: [{
messageId: "extraSemi",
type: "ExportAllDeclaration",
+ line: 1,
column: 20,
+ endLine: 1,
endColumn: 21
}]
},
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
messageId: "extraSemi",
- type: "ExportNamedDeclaration"
+ type: "ExportNamedDeclaration",
+ line: 1,
+ column: 26,
+ endLine: 1,
+ endColumn: 27
}]
},
{
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
messageId: "extraSemi",
- type: "ExportNamedDeclaration"
+ type: "ExportNamedDeclaration",
+ line: 1,
+ column: 27,
+ endLine: 1,
+ endColumn: 28
}]
},
{
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
messageId: "extraSemi",
- type: "VariableDeclaration"
+ type: "VariableDeclaration",
+ line: 1,
+ column: 15,
+ endLine: 1,
+ endColumn: 16
}]
},
{
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
messageId: "extraSemi",
- type: "VariableDeclaration"
+ type: "VariableDeclaration",
+ line: 1,
+ column: 15,
+ endLine: 1,
+ endColumn: 16
}]
},
{
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
messageId: "extraSemi",
- type: "VariableDeclaration"
+ type: "VariableDeclaration",
+ line: 1,
+ column: 22,
+ endLine: 1,
+ endColumn: 23
}]
},
{
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
messageId: "extraSemi",
- type: "ExportDefaultDeclaration"
+ type: "ExportDefaultDeclaration",
+ line: 1,
+ column: 26,
+ endLine: 1,
+ endColumn: 27
}]
},
{
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
messageId: "extraSemi",
- type: "ExportDefaultDeclaration"
+ type: "ExportDefaultDeclaration",
+ line: 1,
+ column: 34,
+ endLine: 1,
+ endColumn: 35
}]
},
{
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
messageId: "extraSemi",
- type: "ExportDefaultDeclaration"
+ type: "ExportDefaultDeclaration",
+ line: 1,
+ column: 24,
+ endLine: 1,
+ endColumn: 25
}]
},
{
parserOptions: { ecmaVersion: 6, sourceType: "module" },
errors: [{
messageId: "extraSemi",
- type: "ExportDefaultDeclaration"
+ type: "ExportDefaultDeclaration",
+ line: 1,
+ column: 25,
+ endLine: 1,
+ endColumn: 26
}]
},
{
options: ["never"],
errors: [{
messageId: "extraSemi",
+ line: 1,
column: 2,
+ endLine: 1,
endColumn: 3
}]
},
].join("\n"),
options: ["never"],
errors: [
- "Extra semicolon.",
- "Unnecessary semicolon."
+ {
+ messageId: "extraSemi",
+ line: 2,
+ column: 6,
+ endLine: 2,
+ endColumn: 7
+ },
+ {
+ message: "Unnecessary semicolon.",
+ line: 3,
+ column: 1,
+ endLine: 3,
+ endColumn: 2
+ }
]
},
`,
options: ["never", { beforeStatementContinuationChars: "always" }],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
- errors: ["Missing semicolon."]
+ errors: [{
+ messageId: "missingSemi",
+ line: 2,
+ column: 34,
+ endLine: 3,
+ endColumn: 1
+ }]
},
{
code: `
`,
options: ["never", { beforeStatementContinuationChars: "always" }],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
- errors: ["Missing semicolon."]
+ errors: [{
+ messageId: "missingSemi",
+ line: 2,
+ column: 38,
+ endLine: 3,
+ endColumn: 1
+ }]
},
{
code: `
`,
options: ["never", { beforeStatementContinuationChars: "always" }],
parserOptions: { ecmaVersion: 2015 },
- errors: ["Missing semicolon."]
+ errors: [{
+ messageId: "missingSemi",
+ line: 3,
+ column: 27,
+ endLine: 4,
+ endColumn: 1
+ }]
},
{
code: `
}
`,
options: ["never", { beforeStatementContinuationChars: "always" }],
- errors: ["Missing semicolon."]
+ errors: [{
+ messageId: "missingSemi",
+ line: 3,
+ column: 26,
+ endLine: 4,
+ endColumn: 1
+ }]
},
{
code: `
}
`,
options: ["never", { beforeStatementContinuationChars: "always" }],
- errors: ["Missing semicolon."]
+ errors: [{
+ messageId: "missingSemi",
+ line: 3,
+ column: 29,
+ endLine: 4,
+ endColumn: 1
+ }]
},
{
code: `
[1,2,3].forEach(doSomething)
`,
options: ["never", { beforeStatementContinuationChars: "always" }],
- errors: ["Missing semicolon."]
+ errors: [{
+ messageId: "missingSemi",
+ line: 2,
+ column: 29,
+ endLine: 3,
+ endColumn: 1
+ }]
},
{
code: `
`,
options: ["never", { beforeStatementContinuationChars: "always" }],
parserOptions: { ecmaVersion: 2015 },
- errors: ["Missing semicolon."]
+ errors: [{
+ messageId: "missingSemi",
+ line: 2,
+ column: 35,
+ endLine: 3,
+ endColumn: 1
+ }]
},
{
code: `
`,
options: ["never", { beforeStatementContinuationChars: "never" }],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
- errors: ["Extra semicolon."]
+ errors: [{
+ messageId: "extraSemi",
+ line: 2,
+ column: 34,
+ endLine: 2,
+ endColumn: 35
+ }]
},
{
code: `
`,
options: ["never", { beforeStatementContinuationChars: "never" }],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
- errors: ["Extra semicolon."]
+ errors: [{
+ messageId: "extraSemi",
+ line: 2,
+ column: 38,
+ endLine: 2,
+ endColumn: 39
+ }]
},
{
code: `
`,
options: ["never", { beforeStatementContinuationChars: "never" }],
parserOptions: { ecmaVersion: 2015 },
- errors: ["Extra semicolon."]
+ errors: [{
+ messageId: "extraSemi",
+ line: 3,
+ column: 27,
+ endLine: 3,
+ endColumn: 28
+ }]
},
{
code: `
}
`,
options: ["never", { beforeStatementContinuationChars: "never" }],
- errors: ["Extra semicolon."]
+ errors: [{
+ messageId: "extraSemi",
+ line: 3,
+ column: 26,
+ endLine: 3,
+ endColumn: 27
+ }]
},
{
code: `
}
`,
options: ["never", { beforeStatementContinuationChars: "never" }],
- errors: ["Extra semicolon."]
+ errors: [{
+ messageId: "extraSemi",
+ line: 3,
+ column: 29,
+ endLine: 3,
+ endColumn: 30
+ }]
},
{
code: `
[1,2,3].forEach(doSomething)
`,
options: ["never", { beforeStatementContinuationChars: "never" }],
- errors: ["Extra semicolon."]
+ errors: [{
+ messageId: "extraSemi",
+ line: 2,
+ column: 29,
+ endLine: 2,
+ endColumn: 30
+ }]
},
{
code: `
`,
options: ["never", { beforeStatementContinuationChars: "never" }],
parserOptions: { ecmaVersion: 2015 },
- errors: ["Extra semicolon."]
+ errors: [{
+ messageId: "extraSemi",
+ line: 2,
+ column: 35,
+ endLine: 2,
+ endColumn: 36
+ }]
},
{
code: `
`,
options: ["never", { beforeStatementContinuationChars: "never" }],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
- errors: ["Extra semicolon."]
+ errors: [{
+ messageId: "extraSemi",
+ line: 3,
+ column: 17,
+ endLine: 3,
+ endColumn: 18
+ }]
},
{
code: `
`,
options: ["never", { beforeStatementContinuationChars: "never" }],
parserOptions: { ecmaVersion: 6, sourceType: "module" },
- errors: ["Extra semicolon."]
+ errors: [{
+ messageId: "extraSemi",
+ line: 3,
+ column: 17,
+ endLine: 3,
+ endColumn: 18
+ }]
},
{
code: `
}
`,
options: ["never", { beforeStatementContinuationChars: "never" }],
- errors: ["Extra semicolon."]
+ errors: [{
+ messageId: "extraSemi",
+ line: 4,
+ column: 21,
+ endLine: 4,
+ endColumn: 22
+ }]
},
{
code: `
}
`,
options: ["never", { beforeStatementContinuationChars: "never" }],
- errors: ["Extra semicolon."]
+ errors: [{
+ messageId: "extraSemi",
+ line: 4,
+ column: 21,
+ endLine: 4,
+ endColumn: 22
+ }]
},
{
code: `
}
`,
options: ["never", { beforeStatementContinuationChars: "never" }],
- errors: ["Extra semicolon."]
+ errors: [{
+ messageId: "extraSemi",
+ line: 4,
+ column: 21,
+ endLine: 4,
+ endColumn: 22
+ }]
},
{
code: `
[1,2,3].forEach(doSomething)
`,
options: ["never", { beforeStatementContinuationChars: "never" }],
- errors: ["Extra semicolon."]
+ errors: [{
+ messageId: "extraSemi",
+ line: 3,
+ column: 17,
+ endLine: 3,
+ endColumn: 18
+ }]
},
{
code: `
`,
options: ["never", { beforeStatementContinuationChars: "never" }],
parserOptions: { ecmaVersion: 2015 },
- errors: ["Extra semicolon."]
+ errors: [{
+ messageId: "extraSemi",
+ line: 3,
+ column: 17,
+ endLine: 3,
+ endColumn: 18
+ }]
+ },
+
+ // Class fields
+ {
+ code: "class C { foo }",
+ output: "class C { foo; }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "missingSemi",
+ line: 1,
+ column: 14,
+ endLine: 1,
+ endColumn: 15
+ }]
+ },
+ {
+ code: "class C { foo }",
+ output: "class C { foo; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "missingSemi",
+ line: 1,
+ column: 14,
+ endLine: 1,
+ endColumn: 15
+ }]
+ },
+ {
+ code: "class C { foo; }",
+ output: "class C { foo }",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "extraSemi",
+ line: 1,
+ column: 14,
+ endLine: 1,
+ endColumn: 15
+ }]
+ },
+ {
+ code: "class C { foo\n[bar]; }",
+ output: "class C { foo;\n[bar]; }",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "missingSemi",
+ line: 1,
+ column: 14,
+ endLine: 2,
+ endColumn: 1
+ }]
+ },
+
+ // class fields
+ {
+ code: "class C { [get];\nfoo\n}",
+ output: "class C { [get]\nfoo\n}",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "extraSemi",
+ line: 1,
+ column: 16,
+ endLine: 1,
+ endColumn: 17
+ }]
+ },
+ {
+ code: "class C { [set];\nfoo\n}",
+ output: "class C { [set]\nfoo\n}",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "extraSemi",
+ line: 1,
+ column: 16,
+ endLine: 1,
+ endColumn: 17
+ }]
+ },
+ {
+ code: "class C { #get;\nfoo\n}",
+ output: "class C { #get\nfoo\n}",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "extraSemi",
+ line: 1,
+ column: 15,
+ endLine: 1,
+ endColumn: 16
+ }]
+ },
+ {
+ code: "class C { #set;\nfoo\n}",
+ output: "class C { #set\nfoo\n}",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "extraSemi",
+ line: 1,
+ column: 15,
+ endLine: 1,
+ endColumn: 16
+ }]
+ },
+ {
+ code: "class C { #static;\nfoo\n}",
+ output: "class C { #static\nfoo\n}",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "extraSemi",
+ line: 1,
+ column: 18,
+ endLine: 1,
+ endColumn: 19
+ }]
+ },
+ {
+ code: "class C { get=1;\nfoo\n}",
+ output: "class C { get=1\nfoo\n}",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "extraSemi",
+ line: 1,
+ column: 16,
+ endLine: 1,
+ endColumn: 17
+ }]
+ },
+ {
+ code: "class C { static static;\nfoo\n}",
+ output: "class C { static static\nfoo\n}",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "extraSemi",
+ line: 1,
+ column: 24,
+ endLine: 1,
+ endColumn: 25
+ }]
+ },
+ {
+ code: "class C { static;\n}",
+ output: "class C { static\n}",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "extraSemi",
+ line: 1,
+ column: 17,
+ endLine: 1,
+ endColumn: 18
+ }]
}
]
});
//------------------------------------------------------------------------------
const ruleTester = new RuleTester(),
+ alwaysArgs = ["always"],
neverArgs = ["never"],
functionsOnlyArgs = [{ functions: "always", keywords: "never", classes: "never" }],
keywordOnlyArgs = [{ functions: "never", keywords: "always", classes: "never" }],
"if(a) {}else{}",
{ code: "if(a){}else {}", options: neverArgs },
{ code: "try {}catch(a){}", options: functionsOnlyArgs },
- { code: "export default class{}", options: classesOnlyArgs, parserOptions: { ecmaVersion: 6, sourceType: "module" } }
+ { code: "export default class{}", options: classesOnlyArgs, parserOptions: { ecmaVersion: 6, sourceType: "module" } },
+
+ // https://github.com/eslint/eslint/issues/15082
+ { code: "switch(x) { case 9:{ break; } }", options: alwaysArgs },
+ { code: "switch(x){ case 9: { break; } }", options: neverArgs },
+ { code: "switch(x) { case (9):{ break; } }", options: alwaysArgs },
+ { code: "switch(x){ case (9): { break; } }", options: neverArgs },
+ { code: "switch(x) { default:{ break; } }", options: alwaysArgs },
+ { code: "switch(x){ default: { break; } }", options: neverArgs },
+
+ // not conflict with `keyword-spacing`
+ {
+ code: "(class{ static{} })",
+ options: ["always"],
+ parserOptions: { ecmaVersion: 2022 }
+ },
+ {
+ code: "(class { static {} })",
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 }
+ }
],
invalid: [
{
options: neverArgs,
parser: fixtureParser("space-before-blocks", "return-type-keyword-2"),
errors: [expectedNoSpacingError]
+ },
+
+ // https://github.com/eslint/eslint/issues/15082 regression tests (only blocks after switch case colons should be excluded)
+ {
+ code: "label:{}",
+ output: "label: {}",
+ options: alwaysArgs,
+ errors: [expectedSpacingError]
+ },
+ {
+ code: "label: {}",
+ output: "label:{}",
+ options: neverArgs,
+ errors: [expectedNoSpacingError]
+ },
+ {
+ code: "switch(x) { case 9: label:{ break; } }",
+ output: "switch(x) { case 9: label: { break; } }",
+ options: alwaysArgs,
+ errors: [expectedSpacingError]
+ },
+ {
+ code: "switch(x){ case 9: label: { break; } }",
+ output: "switch(x){ case 9: label:{ break; } }",
+ options: neverArgs,
+ errors: [expectedNoSpacingError]
+ },
+ {
+ code: "switch(x) { case 9: if(y){ break; } }",
+ output: "switch(x) { case 9: if(y) { break; } }",
+ options: alwaysArgs,
+ errors: [expectedSpacingError]
+ },
+ {
+ code: "switch(x){ case 9: if(y) { break; } }",
+ output: "switch(x){ case 9: if(y){ break; } }",
+ options: neverArgs,
+ errors: [expectedNoSpacingError]
+ },
+ {
+ code: "switch(x) { case 9: y;{ break; } }",
+ output: "switch(x) { case 9: y; { break; } }",
+ options: alwaysArgs,
+ errors: [expectedSpacingError]
+ },
+ {
+ code: "switch(x){ case 9: y; { break; } }",
+ output: "switch(x){ case 9: y;{ break; } }",
+ options: neverArgs,
+ errors: [expectedNoSpacingError]
+ },
+ {
+ code: "switch(x) { case 9: switch(y){} }",
+ output: "switch(x) { case 9: switch(y) {} }",
+ options: alwaysArgs,
+ errors: [expectedSpacingError]
+ },
+ {
+ code: "switch(x){ case 9: switch(y) {} }",
+ output: "switch(x){ case 9: switch(y){} }",
+ options: neverArgs,
+ errors: [expectedNoSpacingError]
}
]
});
{ RuleTester } = require("../../../lib/rule-tester"),
parser = require("../../fixtures/fixture-parser");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester();
ruleTester.run("space-infix-ops", rule, {
// TypeScript Type Aliases
{ code: "type Foo<T> = T;", parser: parser("typescript-parsers/type-alias"), parserOptions: { ecmaVersion: 6 } },
+ // Logical Assignments
{ code: "a &&= b", parserOptions: { ecmaVersion: 2021 } },
{ code: "a ||= b", parserOptions: { ecmaVersion: 2021 } },
- { code: "a ??= b", parserOptions: { ecmaVersion: 2021 } }
+ { code: "a ??= b", parserOptions: { ecmaVersion: 2021 } },
+
+ // Class Fields
+ { code: "class C { a; }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { a = b; }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { 'a' = b; }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { [a] = b; }", parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { #a = b; }", parserOptions: { ecmaVersion: 2022 } }
],
invalid: [
{
}]
},
+ // Logical Assignments
{
code: "a&&=b",
output: "a &&= b",
endColumn: 5,
type: "AssignmentExpression"
}]
+ },
+
+ // Class Fields
+ {
+ code: "class C { a=b; }",
+ output: "class C { a = b; }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "missingSpace",
+ data: { operator: "=" },
+ line: 1,
+ column: 12,
+ endLine: 1,
+ endColumn: 13,
+ type: "PropertyDefinition"
+ }]
+ },
+ {
+ code: "class C { [a ]= b; }",
+ output: "class C { [a ] = b; }",
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "missingSpace",
+ data: { operator: "=" },
+ line: 1,
+ column: 15,
+ endLine: 1,
+ endColumn: 16,
+ type: "PropertyDefinition"
+ }]
}
]
});
code: "function *foo () { yield(0) }",
options: [{ words: false, overrides: { yield: false } }],
parserOptions: { ecmaVersion: 6 }
+ },
+ {
+ code: "class C { #x; *foo(bar) { yield#x in bar; } }",
+ options: [{ words: false }],
+ parserOptions: { ecmaVersion: 2022 }
}
],
line: 1,
column: 24
}]
+ },
+ {
+ code: "class C { #x; *foo(bar) { yield #x in bar; } }",
+ output: "class C { #x; *foo(bar) { yield#x in bar; } }",
+ options: [{ words: false }],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{
+ messageId: "unexpectedAfterWord",
+ data: { word: "yield" },
+ type: "YieldExpression",
+ line: 1,
+ column: 27
+ }]
}
]
});
*/
"use strict";
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
const rule = require("../../../lib/rules/spaced-comment"),
{ RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester(),
validShebangProgram = "#!/path/to/node\nvar a = 3;";
const rule = require("../../../lib/rules/strict"),
{ RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester();
ruleTester.run("strict", rule, {
"function foo() { 'use strict'; return; }",
{ code: "'use strict'; function foo() { return; }", parserOptions: { ecmaFeatures: { globalReturn: true } } },
{ code: "function foo() { return; }", parserOptions: { ecmaVersion: 6, sourceType: "module" } },
- { code: "function foo() { return; }", parserOptions: { ecmaFeatures: { impliedStrict: true } } }
+ { code: "function foo() { return; }", parserOptions: { ecmaFeatures: { impliedStrict: true } } },
+
+ // class static blocks do not have directive prologues, therefore this rule should never require od disallow "use strict" statement in them.
+ { code: "'use strict'; class C { static { foo; } }", options: ["global"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "'use strict'; class C { static { 'use strict'; } }", options: ["global"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "'use strict'; class C { static { 'use strict'; 'use strict'; } }", options: ["global"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { foo; } }", options: ["function"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { 'use strict'; } }", options: ["function"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { 'use strict'; 'use strict'; } }", options: ["function"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { foo; } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { 'use strict'; } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { 'use strict'; 'use strict'; } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } },
+ { code: "class C { static { 'use strict'; } }", options: ["safe"], parserOptions: { ecmaVersion: 2022, sourceType: "module" } },
+ { code: "class C { static { 'use strict'; } }", options: ["safe"], parserOptions: { ecmaVersion: 2022, ecmaFeatures: { impliedStrict: true } } }
],
invalid: [
parserOptions: { ecmaVersion: 6 },
errors: [{ messageId: "unnecessaryInClasses", type: "ExpressionStatement" }]
},
-
+ {
+ code: "class A { field = () => { \"use strict\"; } }",
+ output: "class A { field = () => { } }",
+ options: ["function"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "unnecessaryInClasses", type: "ExpressionStatement" }]
+ },
+ {
+ code: "class A { field = function() { \"use strict\"; } }",
+ output: "class A { field = function() { } }",
+ options: ["function"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "unnecessaryInClasses", type: "ExpressionStatement" }]
+ },
// "safe" mode corresponds to "global" if ecmaFeatures.globalReturn is true, otherwise "function"
{
options: ["function"],
parserOptions: { ecmaVersion: 6 },
errors: ["Use the function form of 'use strict'."]
- }
+ },
+ // functions inside class static blocks should be checked
+ {
+ code: "'use strict'; class C { static { function foo() { \n'use strict'; } } }",
+ output: null,
+ options: ["global"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "global", line: 2 }]
+ },
+ {
+ code: "class C { static { function foo() { \n'use strict'; } } }",
+ output: null,
+ options: ["never"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "never", line: 2 }]
+ },
+ {
+ code: "class C { static { function foo() { \n'use strict'; } } }",
+ output: "class C { static { function foo() { \n } } }",
+ options: ["safe"],
+ parserOptions: { ecmaVersion: 2022, sourceType: "module" },
+ errors: [{ messageId: "module", line: 2 }]
+ },
+ {
+ code: "class C { static { function foo() { \n'use strict'; } } }",
+ output: "class C { static { function foo() { \n } } }",
+ options: ["safe"],
+ parserOptions: { ecmaVersion: 2022, ecmaFeatures: { impliedStrict: true } },
+ errors: [{ messageId: "implied", line: 2 }]
+ },
+ {
+ code: "function foo() {'use strict'; class C { static { function foo() { \n'use strict'; } } } }",
+ output: "function foo() {'use strict'; class C { static { function foo() { \n } } } }",
+ options: ["function"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "unnecessary", line: 2 }]
+ },
+ {
+ code: "class C { static { function foo() { \n'use strict'; } } }",
+ output: "class C { static { function foo() { \n } } }",
+ options: ["function"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [{ messageId: "unnecessaryInClasses", line: 2 }]
+ },
+ {
+ code: "class C { static { function foo() { \n'use strict';\n'use strict'; } } }",
+ output: "class C { static { function foo() { \n\n } } }",
+ options: ["function"],
+ parserOptions: { ecmaVersion: 2022 },
+ errors: [
+ { messageId: "unnecessaryInClasses", line: 2 },
+ { messageId: "multiple", line: 3 }
+ ]
+ }
]
});
const rule = require("../../../lib/rules/symbol-description");
const { RuleTester } = require("../../../lib/rule-tester");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester({ env: { es6: true } });
ruleTester.run("symbol-description", rule, {
code: "var a = 123;",
output: "\uFEFFvar a = 123;",
options: ["always"],
- errors: [expectedError]
+ errors: [{
+ ...expectedError,
+ line: 1,
+ column: 1,
+ endLine: void 0,
+ endColumn: void 0
+
+ }]
},
{
code: " // here's a comment \nvar a = 123;",
output: "\uFEFF // here's a comment \nvar a = 123;",
options: ["always"],
- errors: [expectedError]
+ errors: [{
+ ...expectedError,
+ line: 1,
+ column: 1,
+ endLine: void 0,
+ endColumn: void 0
+
+ }]
},
{
code: "\uFEFF var a = 123;",
output: " var a = 123;",
- errors: [unexpectedError]
+ errors: [{
+ ...unexpectedError,
+ line: 1,
+ column: 1,
+ endLine: void 0,
+ endColumn: void 0
+
+ }]
},
{
code: "\uFEFF var a = 123;",
output: " var a = 123;",
options: ["never"],
- errors: [unexpectedError]
+ errors: [{
+ ...unexpectedError,
+ line: 1,
+ column: 1,
+ endLine: void 0,
+ endColumn: void 0
+
+ }]
}
]
});
"foo(NaN / 2)",
"foo(2 / NaN)",
"var x; if (x = NaN) { }",
+ "var x = Number.NaN;",
+ "isNaN(Number.NaN) === true;",
+ "Number.isNaN(Number.NaN) === true;",
+ "foo(Number.NaN + 1);",
+ "foo(1 + Number.NaN);",
+ "foo(Number.NaN - 1)",
+ "foo(1 - Number.NaN)",
+ "foo(Number.NaN * 2)",
+ "foo(2 * Number.NaN)",
+ "foo(Number.NaN / 2)",
+ "foo(2 / Number.NaN)",
+ "var x; if (x = Number.NaN) { }",
+ "x === Number[NaN];",
//------------------------------------------------------------------------------
// enforceForSwitchCase
code: "switch(foo) { case bar: break; case 1: break; default: break; }",
options: [{ enforceForSwitchCase: true }]
},
+ {
+ code: "switch(Number.NaN) { case foo: break; }",
+ options: [{ enforceForSwitchCase: false }]
+ },
+ {
+ code: "switch(foo) { case Number.NaN: break; }",
+ options: [{ enforceForSwitchCase: false }]
+ },
+ {
+ code: "switch(NaN) { case Number.NaN: break; }",
+ options: [{ enforceForSwitchCase: false }]
+ },
+ {
+ code: "switch(foo) { case bar: break; case Number.NaN: break; default: break; }",
+ options: [{ enforceForSwitchCase: false }]
+ },
+ {
+ code: "switch(foo) { case bar: Number.NaN; }",
+ options: [{ enforceForSwitchCase: true }]
+ },
+ {
+ code: "switch(foo) { default: Number.NaN; }",
+ options: [{ enforceForSwitchCase: true }]
+ },
+ {
+ code: "switch(Number.Nan) {}",
+ options: [{ enforceForSwitchCase: true }]
+ },
+ {
+ code: "switch('Number.NaN') { default: break; }",
+ options: [{ enforceForSwitchCase: true }]
+ },
+ {
+ code: "switch(foo(Number.NaN)) {}",
+ options: [{ enforceForSwitchCase: true }]
+ },
+ {
+ code: "switch(foo.Number.NaN) {}",
+ options: [{ enforceForSwitchCase: true }]
+ },
+ {
+ code: "switch(foo) { case Number.Nan: break }",
+ options: [{ enforceForSwitchCase: true }]
+ },
+ {
+ code: "switch(foo) { case 'Number.NaN': break }",
+ options: [{ enforceForSwitchCase: true }]
+ },
+ {
+ code: "switch(foo) { case foo(Number.NaN): break }",
+ options: [{ enforceForSwitchCase: true }]
+ },
+ {
+ code: "switch(foo) { case foo.Number.NaN: break }",
+ options: [{ enforceForSwitchCase: true }]
+ },
//------------------------------------------------------------------------------
// enforceForIndexOf
"foo.indexOf(NaN)",
"foo.lastIndexOf(NaN)",
+ "foo.indexOf(Number.NaN)",
+ "foo.lastIndexOf(Number.NaN)",
{
code: "foo.indexOf(NaN)",
options: [{}]
{
code: "foo.lastIndexOf(NaN())",
options: [{ enforceForIndexOf: true }]
+ },
+ {
+ code: "foo.indexOf(Number.NaN)",
+ options: [{}]
+ },
+ {
+ code: "foo.lastIndexOf(Number.NaN)",
+ options: [{}]
+ },
+ {
+ code: "foo.indexOf(Number.NaN)",
+ options: [{ enforceForIndexOf: false }]
+ },
+ {
+ code: "foo.lastIndexOf(Number.NaN)",
+ options: [{ enforceForIndexOf: false }]
+ },
+ {
+ code: "indexOf(Number.NaN)",
+ options: [{ enforceForIndexOf: true }]
+ },
+ {
+ code: "lastIndexOf(Number.NaN)",
+ options: [{ enforceForIndexOf: true }]
+ },
+ {
+ code: "new foo.indexOf(Number.NaN)",
+ options: [{ enforceForIndexOf: true }]
+ },
+ {
+ code: "foo.bar(Number.NaN)",
+ options: [{ enforceForIndexOf: true }]
+ },
+ {
+ code: "foo.IndexOf(Number.NaN)",
+ options: [{ enforceForIndexOf: true }]
+ },
+ {
+ code: "foo[indexOf](Number.NaN)",
+ options: [{ enforceForIndexOf: true }]
+ },
+ {
+ code: "foo[lastIndexOf](Number.NaN)",
+ options: [{ enforceForIndexOf: true }]
+ },
+ {
+ code: "indexOf.foo(Number.NaN)",
+ options: [{ enforceForIndexOf: true }]
+ },
+ {
+ code: "foo.lastIndexOf(Number.Nan)",
+ options: [{ enforceForIndexOf: true }]
+ },
+ {
+ code: "foo.indexOf(a, Number.NaN)",
+ options: [{ enforceForIndexOf: true }]
+ },
+ {
+ code: "foo.lastIndexOf(Number.NaN, b)",
+ options: [{ enforceForIndexOf: true }]
+ },
+ {
+ code: "foo.lastIndexOf(Number.NaN, NaN)",
+ options: [{ enforceForIndexOf: true }]
+ },
+ {
+ code: "foo.indexOf(...Number.NaN)",
+ options: [{ enforceForIndexOf: true }],
+ parserOptions: { ecmaVersion: 6 }
+ },
+ {
+ code: "foo.lastIndexOf(Number.NaN())",
+ options: [{ enforceForIndexOf: true }]
}
],
invalid: [
code: "\"abc\" >= NaN;",
errors: [comparisonError]
},
+ {
+ code: "123 == Number.NaN;",
+ errors: [comparisonError]
+ },
+ {
+ code: "123 === Number.NaN;",
+ errors: [comparisonError]
+ },
+ {
+ code: "Number.NaN === \"abc\";",
+ errors: [comparisonError]
+ },
+ {
+ code: "Number.NaN == \"abc\";",
+ errors: [comparisonError]
+ },
+ {
+ code: "123 != Number.NaN;",
+ errors: [comparisonError]
+ },
+ {
+ code: "123 !== Number.NaN;",
+ errors: [comparisonError]
+ },
+ {
+ code: "Number.NaN !== \"abc\";",
+ errors: [comparisonError]
+ },
+ {
+ code: "Number.NaN != \"abc\";",
+ errors: [comparisonError]
+ },
+ {
+ code: "Number.NaN < \"abc\";",
+ errors: [comparisonError]
+ },
+ {
+ code: "\"abc\" < Number.NaN;",
+ errors: [comparisonError]
+ },
+ {
+ code: "Number.NaN > \"abc\";",
+ errors: [comparisonError]
+ },
+ {
+ code: "\"abc\" > Number.NaN;",
+ errors: [comparisonError]
+ },
+ {
+ code: "Number.NaN <= \"abc\";",
+ errors: [comparisonError]
+ },
+ {
+ code: "\"abc\" <= Number.NaN;",
+ errors: [comparisonError]
+ },
+ {
+ code: "Number.NaN >= \"abc\";",
+ errors: [comparisonError]
+ },
+ {
+ code: "\"abc\" >= Number.NaN;",
+ errors: [comparisonError]
+ },
+ {
+ code: "x === Number?.NaN;",
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [comparisonError]
+ },
+ {
+ code: "x === Number['NaN'];",
+ errors: [comparisonError]
+ },
//------------------------------------------------------------------------------
// enforceForSwitchCase
{ messageId: "caseNaN", type: "SwitchCase", column: 15 }
]
},
+ {
+ code: "switch(Number.NaN) { case foo: break; }",
+ errors: [{ messageId: "switchNaN", type: "SwitchStatement", column: 1 }]
+ },
+ {
+ code: "switch(foo) { case Number.NaN: break; }",
+ errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 15 }]
+ },
+ {
+ code: "switch(Number.NaN) { case foo: break; }",
+ options: [{}],
+ errors: [{ messageId: "switchNaN", type: "SwitchStatement", column: 1 }]
+ },
+ {
+ code: "switch(foo) { case Number.NaN: break; }",
+ options: [{}],
+ errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 15 }]
+ },
+ {
+ code: "switch(Number.NaN) {}",
+ options: [{ enforceForSwitchCase: true }],
+ errors: [{ messageId: "switchNaN", type: "SwitchStatement", column: 1 }]
+ },
+ {
+ code: "switch(Number.NaN) { case foo: break; }",
+ options: [{ enforceForSwitchCase: true }],
+ errors: [{ messageId: "switchNaN", type: "SwitchStatement", column: 1 }]
+ },
+ {
+ code: "switch(Number.NaN) { default: break; }",
+ options: [{ enforceForSwitchCase: true }],
+ errors: [{ messageId: "switchNaN", type: "SwitchStatement", column: 1 }]
+ },
+ {
+ code: "switch(Number.NaN) { case foo: break; default: break; }",
+ options: [{ enforceForSwitchCase: true }],
+ errors: [{ messageId: "switchNaN", type: "SwitchStatement", column: 1 }]
+ },
+ {
+ code: "switch(foo) { case Number.NaN: }",
+ options: [{ enforceForSwitchCase: true }],
+ errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 15 }]
+ },
+ {
+ code: "switch(foo) { case Number.NaN: break; }",
+ options: [{ enforceForSwitchCase: true }],
+ errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 15 }]
+ },
+ {
+ code: "switch(foo) { case (Number.NaN): break; }",
+ options: [{ enforceForSwitchCase: true }],
+ errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 15 }]
+ },
+ {
+ code: "switch(foo) { case bar: break; case Number.NaN: break; default: break; }",
+ options: [{ enforceForSwitchCase: true }],
+ errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 32 }]
+ },
+ {
+ code: "switch(foo) { case bar: case Number.NaN: default: break; }",
+ options: [{ enforceForSwitchCase: true }],
+ errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 25 }]
+ },
+ {
+ code: "switch(foo) { case bar: break; case NaN: break; case baz: break; case Number.NaN: break; }",
+ options: [{ enforceForSwitchCase: true }],
+ errors: [
+ { messageId: "caseNaN", type: "SwitchCase", column: 32 },
+ { messageId: "caseNaN", type: "SwitchCase", column: 66 }
+ ]
+ },
+ {
+ code: "switch(Number.NaN) { case Number.NaN: break; }",
+ options: [{ enforceForSwitchCase: true }],
+ errors: [
+ { messageId: "switchNaN", type: "SwitchStatement", column: 1 },
+ { messageId: "caseNaN", type: "SwitchCase", column: 22 }
+ ]
+ },
//------------------------------------------------------------------------------
// enforceForIndexOf
options: [{ enforceForIndexOf: true }],
parserOptions: { ecmaVersion: 2020 },
errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }]
+ },
+ {
+ code: "foo.indexOf(Number.NaN)",
+ options: [{ enforceForIndexOf: true }],
+ errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "indexOf" } }]
+ },
+ {
+ code: "foo.lastIndexOf(Number.NaN)",
+ options: [{ enforceForIndexOf: true }],
+ errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "lastIndexOf" } }]
+ },
+ {
+ code: "foo['indexOf'](Number.NaN)",
+ options: [{ enforceForIndexOf: true }],
+ errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "indexOf" } }]
+ },
+ {
+ code: "foo['lastIndexOf'](Number.NaN)",
+ options: [{ enforceForIndexOf: true }],
+ errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "lastIndexOf" } }]
+ },
+ {
+ code: "foo().indexOf(Number.NaN)",
+ options: [{ enforceForIndexOf: true }],
+ errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "indexOf" } }]
+ },
+ {
+ code: "foo.bar.lastIndexOf(Number.NaN)",
+ options: [{ enforceForIndexOf: true }],
+ errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "lastIndexOf" } }]
+ },
+ {
+ code: "foo.indexOf?.(Number.NaN)",
+ options: [{ enforceForIndexOf: true }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }]
+ },
+ {
+ code: "foo?.indexOf(Number.NaN)",
+ options: [{ enforceForIndexOf: true }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }]
+ },
+ {
+ code: "(foo?.indexOf)(Number.NaN)",
+ options: [{ enforceForIndexOf: true }],
+ parserOptions: { ecmaVersion: 2020 },
+ errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }]
}
]
});
* Asserts the node is NOT a directive comment
* @param {ASTNode} node node to assert
* @returns {void}
- *
*/
function assertFalse(node) {
assert.isFalse(astUtils.isDirectiveComment(node));
* Asserts the node is a directive comment
* @param {ASTNode} node node to assert
* @returns {void}
- *
*/
function assertTrue(node) {
assert.isTrue(astUtils.isDirectiveComment(node));
describe("getStaticStringValue", () => {
- /* eslint-disable quote-props */
+ /* eslint-disable quote-props -- Make consistent here for readability */
const expectedResults = {
// string literals
"this": null,
"(function () {})": null
};
- /* eslint-enable quote-props */
+ /* eslint-enable quote-props -- Make consistent here for readability */
Object.keys(expectedResults).forEach(key => {
it(`should return ${expectedResults[key]} for ${key}`, () => {
"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 { 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"
};
Object.keys(expectedResults).forEach(key => {
})
})));
- linter.verify(key, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 8 } });
+ linter.verify(key, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 13 } });
});
});
});
"class A { static *foo() {} }": [10, 21],
"class A { static async foo() {} }": [10, 26],
"class A { static get foo() {} }": [10, 24],
- "class A { static set foo(a) {} }": [10, 24]
+ "class A { static set foo(a) {} }": [10, 24],
+ "class A { foo = function() {}; }": [10, 24],
+ "class A { foo = function bar() {}; }": [10, 28],
+ "class A { static foo = function() {}; }": [10, 31],
+ "class A { foo = () => {}; }": [10, 16],
+ "class A { foo = arg => {}; }": [10, 16]
};
Object.keys(expectedResults).forEach(key => {
})
})));
- linter.verify(key, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 8 } }, "test.js", true);
+ linter.verify(key, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 13 } }, "test.js", true);
});
});
});
describe("getNextLocation", () => {
- /* eslint-disable quote-props */
+ /* eslint-disable quote-props -- Make consistent here for readability */
const expectedResults = {
"": [[1, 0], null],
"\n": [[1, 0], [2, 0], null],
"a\t": [[1, 0], [1, 1], [1, 2], null],
"a \n": [[1, 0], [1, 1], [1, 2], [2, 0], null]
};
- /* eslint-enable quote-props */
+ /* eslint-enable quote-props -- Make consistent here for readability */
Object.keys(expectedResults).forEach(code => {
it(`should return expected locations for "${code}".`, () => {
[["(", "123invalidtoken"], false],
[["(", "1n"], true],
[["1n", "+"], true],
- [["1n", "in"], false]
+ [["1n", "in"], false],
+ [["return", "#x"], true],
+ [["yield", "#x"], true],
+ [["get", "#x"], true]
]);
CASES.forEach((expectedResult, tokenStrings) => {
},
nodeB: {
type: "Literal",
- value: /(?:)/, // eslint-disable-line require-unicode-regexp
+ value: /(?:)/, // eslint-disable-line require-unicode-regexp -- Checking non-Unicode regex
regex: { pattern: "(?:)", flags: "" }
},
expected: false
describe("hasOctalOrNonOctalDecimalEscapeSequence", () => {
- /* eslint-disable quote-props */
+ /* eslint-disable quote-props -- Make consistent here for readability */
const expectedResults = {
"\\1": true,
"\\2": true,
"foo\\\nbar": false,
"128\\\n349": false
};
- /* eslint-enable quote-props */
+ /* eslint-enable quote-props -- Make consistent here for readability */
Object.keys(expectedResults).forEach(key => {
it(`should return ${expectedResults[key]} for ${key}`, () => {
ecmaVersion: 6,
sourceType: "module"
}
+ },
+ {
+ code: [
+ "class C {",
+ " static {",
+ " var x;",
+ " }",
+ "}"
+ ].join("\n"),
+ parserOptions: {
+ ecmaVersion: 2022
+ }
+ },
+ {
+ code: [
+ "class C {",
+ " static {",
+ " var x;",
+ " foo();",
+ " }",
+ "}"
+ ].join("\n"),
+ parserOptions: {
+ ecmaVersion: 2022
+ }
+ },
+ {
+ code: [
+ "class C {",
+ " static {",
+ " var x;",
+ " var y;",
+ " }",
+ "}"
+ ].join("\n"),
+ parserOptions: {
+ ecmaVersion: 2022
+ }
+ },
+ {
+ code: [
+ "class C {",
+ " static {",
+ " var x;",
+ " var y;",
+ " foo();",
+ " }",
+ "}"
+ ].join("\n"),
+ parserOptions: {
+ ecmaVersion: 2022
+ }
+ },
+ {
+ code: [
+ "class C {",
+ " static {",
+ " let x;",
+ " var y;",
+ " }",
+ "}"
+ ].join("\n"),
+ parserOptions: {
+ ecmaVersion: 2022
+ }
+ },
+ {
+ code: [
+ "class C {",
+ " static {",
+ " foo();",
+ " let x;",
+ " }",
+ "}"
+ ].join("\n"),
+ parserOptions: {
+ ecmaVersion: 2022
+ }
}
],
sourceType: "module"
},
errors: [error]
+ },
+ {
+ code: [
+ "class C {",
+ " static {",
+ " foo();",
+ " var x;",
+ " }",
+ "}"
+ ].join("\n"),
+ parserOptions: {
+ ecmaVersion: 2022
+ },
+ errors: [error]
+ },
+ {
+ code: [
+ "class C {",
+ " static {",
+ " 'use strict';", // static blocks do not have directives
+ " var x;",
+ " }",
+ "}"
+ ].join("\n"),
+ parserOptions: {
+ ecmaVersion: 2022
+ },
+ errors: [error]
+ },
+ {
+ code: [
+ "class C {",
+ " static {",
+ " var x;",
+ " foo();",
+ " var y;",
+ " }",
+ "}"
+ ].join("\n"),
+ parserOptions: {
+ ecmaVersion: 2022
+ },
+ errors: [{ ...error, line: 5 }]
+ },
+ {
+ code: [
+ "class C {",
+ " static {",
+ " if (foo) {",
+ " var x;",
+ " }",
+ " }",
+ "}"
+ ].join("\n"),
+ parserOptions: {
+ ecmaVersion: 2022
+ },
+ errors: [error]
+ },
+ {
+ code: [
+ "class C {",
+ " static {",
+ " if (foo)",
+ " var x;",
+ " }",
+ "}"
+ ].join("\n"),
+ parserOptions: {
+ ecmaVersion: 2022
+ },
+ errors: [error]
}
]
});
// Tests
//------------------------------------------------------------------------------
-
const ruleTester = new RuleTester();
ruleTester.run("wrap-regex", rule, {
{ Linter } = require("../../../lib/linter"),
validator = require("../../../lib/shared/config-validator"),
Rules = require("../../../lib/linter/rules");
-const linter = new Linter();
//------------------------------------------------------------------------------
-// Tests
+// Helpers
//------------------------------------------------------------------------------
+const linter = new Linter();
+
/**
* Fake a rule object
* @param {Object} context context passed to the rules by eslint
}
};
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
describe("Validator", () => {
/**
"use strict";
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
const assert = require("chai").assert;
const Traverser = require("../../../lib/shared/traverser");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
describe("Traverser", () => {
it("traverses all keys except 'parent', 'leadingComments', and 'trailingComments'", () => {
const traverser = new Traverser();
--- /dev/null
+/**
+ * @fileoverview Tests for unsupported-api.
+ * @author Nicholas C. Zakas
+ */
+
+"use strict";
+
+//-----------------------------------------------------------------------------
+// Requirements
+//-----------------------------------------------------------------------------
+
+const assert = require("chai").assert,
+ { LazyLoadingRuleMap } = require("../../lib/rules/utils/lazy-loading-rule-map"),
+ api = require("../../lib/unsupported-api");
+
+//-----------------------------------------------------------------------------
+// Tests
+//-----------------------------------------------------------------------------
+
+describe("unsupported-api", () => {
+
+ it("should have FileEnumerator exposed", () => {
+ assert.isFunction(api.FileEnumerator);
+ });
+
+ it("should have builtinRules exposed", () => {
+ assert.instanceOf(api.builtinRules, LazyLoadingRuleMap);
+ });
+
+});
"use strict";
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
const { assert } = require("chai");
const reduceBadExampleSize = require("../../tools/code-sample-minimizer");
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
describe("reduceBadExampleSize()", () => {
it("extracts relevant part of deeply nested code", () => {
const initialCode = `
+++ /dev/null
-/**
- * @fileoverview Tests for internal-consistent-docs-url rule.
- * @author Patrick McElhaney
- */
-
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const rule = require("../../../tools/internal-rules/consistent-docs-url"),
- { RuleTester } = require("../../../lib/rule-tester");
-
-//------------------------------------------------------------------------------
-// Tests
-//------------------------------------------------------------------------------
-
-const ruleTester = new RuleTester();
-
-ruleTester.run("consistent-docs-url", rule, {
- valid: [
-
- // wrong exports format: "internal-no-invalid-meta" reports this already
- [
- "module.exports = function(context) {",
- " return {",
- " Program: function(node) {}",
- " };",
- "};"
- ].join("\n"),
- [
- "module.exports = {",
- " meta: {",
- " docs: {",
- " url: 'https://eslint.org/docs/rules/<input>'",
- " }",
- " },",
- " create: function(context) {",
- " return {};",
- " }",
- "};"
- ].join("\n")
- ],
- invalid: [
- {
- code: [
- "module.exports = {",
- " meta: {",
- " },",
-
- " create: function(context) {",
- " return {};",
- " }",
- "};"
- ].join("\n"),
- errors: [{
- messageId: "missingMetaDocs",
- line: 2,
- column: 5
- }]
- },
- {
- code: [
- "module.exports = {",
- " meta: {",
- " docs: {}",
- " },",
-
- " create: function(context) {",
- " return {};",
- " }",
- "};"
- ].join("\n"),
- errors: [{
- messageId: "missingMetaDocsUrl",
- line: 3,
- column: 9
- }]
- },
- {
- code: [
- "module.exports = {",
- " meta: {",
- " docs: {",
- " url: 'http://example.com/wrong-url'",
- " }",
- " },",
- " create: function(context) {",
- " return {};",
- " }",
- "};"
- ].join("\n"),
- errors: [{
- messageId: "incorrectUrl",
- data: {
- expected: "https://eslint.org/docs/rules/<input>",
- url: "http://example.com/wrong-url"
- },
- line: 4,
- column: 18
- }]
- }
- ]
-});
+++ /dev/null
-/**
- * @fileoverview Tests for consistent-meta-messages rule.
- * @author 薛定谔的猫<hh_2013@foxmail.com>
- */
-
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const rule = require("../../../tools/internal-rules/consistent-meta-messages");
-const { RuleTester } = require("../../../lib/rule-tester");
-
-//------------------------------------------------------------------------------
-// Tests
-//------------------------------------------------------------------------------
-
-const ruleTester = new RuleTester();
-
-ruleTester.run("consistent-meta-messages", rule, {
- valid: [
- `module.exports = {
- meta: {
- messages: {unexpected: "an error occurs."}
- }
- };`
- ],
- invalid: [
- {
- code: `
- module.exports = {
- meta: {}
- };`,
- errors: [{ messageId: "expectedMessages" }]
- }
- ]
-});
"use strict";
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
const rule = require("../../../tools/internal-rules/multiline-comment-style");
const { RuleTester } = require("../../../lib/rule-tester");
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
const ruleTester = new RuleTester();
ruleTester.run("internal-rules/multiline-comment-style", rule, {
" meta: {",
" docs: {",
" description: 'some rule',",
- " category: 'Internal',",
" recommended: false",
" },",
" schema: []",
" meta: {",
" docs: {",
" description: 'some rule',",
- " category: 'Internal',",
" recommended: false",
" },",
" schema: []",
" meta: {",
" docs: {",
" description: 'some rule',",
- " category: 'Internal',",
" recommended: false",
" },",
" schema: [],",
].join("\n")
],
invalid: [
- {
- code: [
- "module.exports = function(context) {",
- " return {",
- " Program: function(node) {}",
- " };",
- "};"
- ].join("\n"),
- errors: [{
- messageId: "incorrectExport",
- line: 1,
- column: 18
- }]
- },
{
code: [
"module.exports = {",
column: 5
}]
},
- {
- code: [
- "module.exports = {",
- " meta: {",
- " docs: {",
- " category: 'Internal',",
- " recommended: false",
- " },",
- " schema: []",
- " },",
-
- " create: function(context) {",
- " return {",
- " Program: function(node) {}",
- " };",
- " }",
- "};"
- ].join("\n"),
- errors: [{
- messageId: "missingMetaDocsDescription",
- line: 2,
- column: 5
- }]
- },
- {
- code: [
- "module.exports = {",
- " meta: {",
- " docs: {",
- " description: 'some rule',",
- " recommended: false",
- " },",
- " schema: []",
- " },",
-
- " create: function(context) {",
- " return {",
- " Program: function(node) {}",
- " };",
- " }",
- "};"
- ].join("\n"),
- errors: [{
- messageId: "missingMetaDocsCategory",
- line: 2,
- column: 5
- }]
- },
{
code: [
"module.exports = {",
" meta: {",
" docs: {",
" description: 'some rule',",
- " category: 'Internal'",
" },",
" schema: []",
" },",
column: 5
}]
},
- {
- code: [
- "module.exports = {",
- " meta: {",
- " docs: {",
- " description: 'some rule',",
- " category: 'Internal',",
- " recommended: false",
- " }",
- " },",
-
- " create: function(context) {",
- " return {",
- " Program: function(node) {}",
- " };",
- " }",
- "};"
- ].join("\n"),
- errors: [{
- messageId: "missingMetaSchema",
- line: 2,
- column: 5
- }]
- },
{
code: "",
errors: [{
+++ /dev/null
-/**
- * @fileoverview Internal rule to enforce meta.docs.url conventions.
- * @author Patrick McElhaney
- */
-
-"use strict";
-
-const path = require("path");
-
-//------------------------------------------------------------------------------
-// Helpers
-//------------------------------------------------------------------------------
-
-/**
- * Gets the property of the Object node passed in that has the name specified.
- * @param {string} property Name of the property to return.
- * @param {ASTNode} node The ObjectExpression node.
- * @returns {ASTNode} The Property node or null if not found.
- */
-function getPropertyFromObject(property, node) {
- const properties = node.properties;
-
- if (!Array.isArray(properties)) {
-
- // if properties is not an array, "internal-no-invalid-meta" will already report this.
- return null;
- }
-
- for (let i = 0; i < properties.length; i++) {
- if (properties[i].key.name === property) {
- return properties[i];
- }
- }
-
- return null;
-}
-
-/**
- * Verifies that the meta.docs.url property is present and has the correct value.
- * @param {RuleContext} context The ESLint rule context.
- * @param {ASTNode} exportsNode ObjectExpression node that the rule exports.
- * @returns {void}
- */
-function checkMetaDocsUrl(context, exportsNode) {
- if (exportsNode.type !== "ObjectExpression") {
-
- // if the exported node is not the correct format, "internal-no-invalid-meta" will already report this.
- return;
- }
-
- const metaProperty = getPropertyFromObject("meta", exportsNode);
- const metaDocs = metaProperty && getPropertyFromObject("docs", metaProperty.value);
- const metaDocsUrl = metaDocs && getPropertyFromObject("url", metaDocs.value);
-
- if (!metaDocs) {
- context.report({
- node: metaProperty,
- messageId: "missingMetaDocs"
- });
- return;
- }
-
- if (!metaDocsUrl) {
- context.report({
- node: metaDocs,
- messageId: "missingMetaDocsUrl"
- });
- return;
- }
-
- const ruleId = path.basename(context.getFilename().replace(/.js$/u, ""));
- const expected = `https://eslint.org/docs/rules/${ruleId}`;
- const url = metaDocsUrl.value.value;
-
- if (url !== expected) {
- context.report({
- node: metaDocsUrl.value,
- messageId: "incorrectUrl",
- data: { expected, url }
- });
- }
-
-}
-
-//------------------------------------------------------------------------------
-// Rule Definition
-//------------------------------------------------------------------------------
-
-module.exports = {
- meta: {
- docs: {
- description: "enforce correct conventions of `meta.docs.url` property in core rules",
- category: "Internal",
- recommended: false
- },
- type: "suggestion",
- schema: [],
- messages: {
- missingMetaDocs: "Rule is missing a meta.docs property.",
- missingMetaDocsUrl: "Rule is missing a meta.docs.url property.",
- incorrectUrl: 'Incorrect url. Expected "{{ expected }}" but got "{{ url }}".'
- }
- },
-
- create(context) {
- return {
- AssignmentExpression(node) {
- if (node.left &&
- node.right &&
- node.left.type === "MemberExpression" &&
- node.left.object.name === "module" &&
- node.left.property.name === "exports") {
-
- checkMetaDocsUrl(context, node.right);
- }
- }
- };
- }
-};
+++ /dev/null
-/**
- * @fileoverview A rule to enforce using `meta.messages` property in core rules
- * @author 薛定谔的猫<hh_2013@foxmail.com>
- */
-
-"use strict";
-
-//------------------------------------------------------------------------------
-// Helpers
-//------------------------------------------------------------------------------
-
-/**
- * Gets the property of the Object node passed in that has the name specified.
- * @param {string} property Name of the property to return.
- * @param {ASTNode} node The ObjectExpression node.
- * @returns {ASTNode} The Property node or null if not found.
- */
-function getPropertyFromObject(property, node) {
- const properties = node.properties;
-
- for (let i = 0; i < properties.length; i++) {
- if (properties[i].key.name === property) {
- return properties[i];
- }
- }
-
- return null;
-}
-
-/**
- * Verifies that the meta.messages property is present.
- * TODO: check it has the correct value
- * @param {RuleContext} context The ESLint rule context.
- * @param {ASTNode} exportsNode ObjectExpression node that the rule exports.
- * @returns {void}
- */
-function checkMetaMessages(context, exportsNode) {
- if (exportsNode.type !== "ObjectExpression") {
-
- // if the exported node is not the correct format, "internal-no-invalid-meta" will already report this.
- return;
- }
-
- const metaProperty = getPropertyFromObject("meta", exportsNode);
- const messages = metaProperty && getPropertyFromObject("messages", metaProperty.value);
-
- if (!messages) {
- context.report({
- node: metaProperty,
- messageId: "expectedMessages"
- });
- }
-}
-
-//------------------------------------------------------------------------------
-// Rule Definition
-//------------------------------------------------------------------------------
-
-module.exports = {
- meta: {
- docs: {
- description: "enforce using `meta.messages` property in core rules",
- category: "Internal",
- recommended: false
- },
- schema: [],
- type: "suggestion",
- messages: {
- expectedMessages: "Expected `meta.messages` property."
- }
- },
-
- create(context) {
- return {
- "AssignmentExpression[left.object.name='module'][left.property.name='exports']"(node) {
- checkMetaMessages(context, node.right);
- }
- };
- }
-};
//------------------------------------------------------------------------------
// The `no-invalid-meta` internal rule has a false positive here.
-// eslint-disable-next-line internal-rules/no-invalid-meta
+// eslint-disable-next-line internal-rules/no-invalid-meta -- Using rule composer
module.exports = ruleComposer.filterReports(
multilineCommentStyle,
(problem, metadata) => {
return Boolean(getPropertyFromObject("docs", metaPropertyNode.value));
}
-/**
- * Whether this `meta` ObjectExpression has a `docs.description` property defined or not.
- * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
- * @returns {boolean} `true` if a `docs.description` property exists.
- */
-function hasMetaDocsDescription(metaPropertyNode) {
- const metaDocs = getPropertyFromObject("docs", metaPropertyNode.value);
-
- return metaDocs && getPropertyFromObject("description", metaDocs.value);
-}
-
-/**
- * Whether this `meta` ObjectExpression has a `docs.category` property defined or not.
- * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
- * @returns {boolean} `true` if a `docs.category` property exists.
- */
-function hasMetaDocsCategory(metaPropertyNode) {
- const metaDocs = getPropertyFromObject("docs", metaPropertyNode.value);
-
- return metaDocs && getPropertyFromObject("category", metaDocs.value);
-}
-
/**
* Whether this `meta` ObjectExpression has a `docs.recommended` property defined or not.
* @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
return metaDocs && getPropertyFromObject("recommended", metaDocs.value);
}
-/**
- * Whether this `meta` ObjectExpression has a `schema` property defined or not.
- * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
- * @returns {boolean} `true` if a `schema` property exists.
- */
-function hasMetaSchema(metaPropertyNode) {
- return getPropertyFromObject("schema", metaPropertyNode.value);
-}
-
/**
* Checks the validity of the meta definition of this rule and reports any errors found.
* @param {RuleContext} context The ESLint rule context.
return;
}
- if (!hasMetaDocsDescription(metaProperty)) {
- context.report({ node: metaProperty, messageId: "missingMetaDocsDescription" });
- return;
- }
-
- if (!hasMetaDocsCategory(metaProperty)) {
- context.report({ node: metaProperty, messageId: "missingMetaDocsCategory" });
- return;
- }
-
if (!hasMetaDocsRecommended(metaProperty)) {
context.report({ node: metaProperty, messageId: "missingMetaDocsRecommended" });
- return;
}
-
- if (!hasMetaSchema(metaProperty)) {
- context.report({ node: metaProperty, messageId: "missingMetaSchema" });
- }
-}
-
-/**
- * Whether this node is the correct format for a rule definition or not.
- * @param {ASTNode} node node that the rule exports.
- * @returns {boolean} `true` if the exported node is the correct format for a rule definition
- */
-function isCorrectExportsFormat(node) {
- return node.type === "ObjectExpression";
}
//------------------------------------------------------------------------------
meta: {
docs: {
description: "enforce correct use of `meta` property in core rules",
- category: "Internal",
recommended: false
},
type: "problem",
messages: {
missingMeta: "Rule is missing a meta property.",
missingMetaDocs: "Rule is missing a meta.docs property.",
- missingMetaDocsDescription: "Rule is missing a meta.docs.description property.",
- missingMetaDocsCategory: "Rule is missing a meta.docs.category property.",
missingMetaDocsRecommended: "Rule is missing a meta.docs.recommended property.",
- missingMetaSchema: "Rule is missing a meta.schema property.",
- noExport: "Rule does not export anything. Make sure rule exports an object according to new rule format.",
- incorrectExport: "Rule does not export an Object. Make sure the rule follows the new rule format."
+ noExport: "Rule does not export anything. Make sure rule exports an object according to new rule format."
}
},
node,
messageId: "noExport"
});
- } else if (!isCorrectExportsFormat(exportsNode)) {
- context.report({
- node: exportsNode,
- messageId: "incorrectExport"
- });
} else {
checkMetaValidity(context, exportsNode);
}
*/
"use strict";
-/* global describe, it */
+/* eslint-env mocha -- Mocha */
//------------------------------------------------------------------------------
// Requirements
"no-unsafe-optional-chaining": "problem",
"no-unused-expressions": "suggestion",
"no-unused-labels": "suggestion",
+ "no-unused-private-class-members": "problem",
"no-unused-vars": "problem",
"no-use-before-define": "problem",
"no-useless-backreference": "problem",
* @returns {string} The HTML for the members list.
*/
function formatTeamMembers(members) {
- /* eslint-disable indent*/
+ /* eslint-disable indent -- Allow deeper template substitution indent */
return stripIndents`
<table><tbody><tr>${
members.map((member, index) => `<td align="center" valign="top" width="11%">
</a>
</td>${(index + 1) % 9 === 0 ? "</tr><tr>" : ""}`).join("")
}</tr></tbody></table>`;
- /* eslint-enable indent*/
+ /* eslint-enable indent -- Allow deeper template substitution indent */
}
/**
function formatSponsors(sponsors) {
const nonEmptySponsors = Object.keys(sponsors).filter(tier => sponsors[tier].length > 0);
- /* eslint-disable indent*/
+ /* eslint-disable indent -- Allow deeper template substitution indent */
return stripIndents`<!--sponsorsstart-->
${
nonEmptySponsors.map(tier => `<h3>${tier[0].toUpperCase()}${tier.slice(1)} Sponsors</h3>
}</p>`).join("")
}
<!--sponsorsend-->`;
- /* eslint-enable indent*/
+ /* eslint-enable indent -- Allow deeper template substitution indent */
}
//-----------------------------------------------------------------------------
}
const typeNode = metaNode.value.properties.find(node => node.key.name === "type");
- const docsNode = metaNode.value.properties.find(node => node.key.name === "docs");
- const categoryNode = docsNode.value.properties.find(node => node.key.name === "category").value;
let ruleType;
- // the rule-types.json file takes highest priority
if (ruleName in ruleTypes) {
ruleType = ruleTypes[ruleName];
- } else {
-
- // otherwise fallback to category
- switch (categoryNode.value) {
- case "Stylistic Issues":
- ruleType = "style";
- break;
-
- case "Possible Errors":
- ruleType = "problem";
- break;
-
- default:
- ruleType = "suggestion";
- }
}
if (typeNode) {