]> git.proxmox.com Git - pve-eslint.git/commitdiff
import eslint 7.28.0
authorThomas Lamprecht <t.lamprecht@proxmox.com>
Tue, 8 Jun 2021 09:43:11 +0000 (11:43 +0200)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Tue, 8 Jun 2021 09:43:11 +0000 (11:43 +0200)
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
178 files changed:
Makefile
eslint/.eslintrc.js
eslint/.github/ISSUE_TEMPLATE/BUG_REPORT.md
eslint/.github/ISSUE_TEMPLATE/QUESTION.md [deleted file]
eslint/.github/ISSUE_TEMPLATE/SECURITY.md [deleted file]
eslint/.github/ISSUE_TEMPLATE/config.yml
eslint/.github/PULL_REQUEST_TEMPLATE.md
eslint/.github/workflows/ci.yml
eslint/CHANGELOG.md
eslint/Makefile.js
eslint/README.md
eslint/bin/eslint.js
eslint/conf/default-cli-options.js
eslint/docs/developer-guide/code-path-analysis.md
eslint/docs/developer-guide/code-path-analysis/README.md
eslint/docs/developer-guide/nodejs-api.md
eslint/docs/developer-guide/selectors.md
eslint/docs/developer-guide/shareable-configs.md
eslint/docs/developer-guide/working-with-plugins.md
eslint/docs/developer-guide/working-with-rules-deprecated.md
eslint/docs/developer-guide/working-with-rules.md
eslint/docs/maintainer-guide/issues.md
eslint/docs/rules/arrow-parens.md
eslint/docs/rules/block-scoped-var.md
eslint/docs/rules/indent.md
eslint/docs/rules/max-lines.md
eslint/docs/rules/no-duplicate-imports.md
eslint/docs/rules/no-implicit-coercion.md
eslint/docs/rules/no-mixed-operators.md
eslint/docs/rules/no-multi-assign.md
eslint/docs/rules/no-restricted-imports.md
eslint/docs/rules/no-sequences.md
eslint/docs/rules/no-undef.md
eslint/docs/rules/no-unsafe-optional-chaining.md
eslint/docs/rules/no-unused-expressions.md
eslint/docs/rules/no-unused-vars.md
eslint/docs/rules/object-curly-newline.md
eslint/docs/rules/prefer-arrow-callback.md
eslint/docs/rules/require-atomic-updates.md
eslint/docs/rules/strict.md
eslint/docs/rules/yield-star-spacing.md
eslint/docs/user-guide/README.md
eslint/docs/user-guide/command-line-interface.md
eslint/docs/user-guide/configuring.md [deleted file]
eslint/docs/user-guide/configuring/README.md [new file with mode: 0644]
eslint/docs/user-guide/configuring/configuration-files.md [new file with mode: 0644]
eslint/docs/user-guide/configuring/ignoring-code.md [new file with mode: 0644]
eslint/docs/user-guide/configuring/language-options.md [new file with mode: 0644]
eslint/docs/user-guide/configuring/plugins.md [new file with mode: 0644]
eslint/docs/user-guide/configuring/rules.md [new file with mode: 0644]
eslint/docs/user-guide/getting-started.md
eslint/karma.conf.js
eslint/lib/cli-engine/cli-engine.js
eslint/lib/cli-engine/file-enumerator.js
eslint/lib/cli-engine/formatters/html-template-message.html [deleted file]
eslint/lib/cli-engine/formatters/html-template-page.html [deleted file]
eslint/lib/cli-engine/formatters/html-template-result.html [deleted file]
eslint/lib/cli-engine/formatters/html.js
eslint/lib/cli-engine/lint-result-cache.js
eslint/lib/cli.js
eslint/lib/eslint/eslint.js
eslint/lib/init/autoconfig.js
eslint/lib/init/config-file.js
eslint/lib/init/config-initializer.js
eslint/lib/init/npm-utils.js
eslint/lib/linter/apply-disable-directives.js
eslint/lib/linter/linter.js
eslint/lib/linter/node-event-generator.js
eslint/lib/linter/report-translator.js
eslint/lib/options.js
eslint/lib/rule-tester/rule-tester.js
eslint/lib/rules/arrow-body-style.js
eslint/lib/rules/comma-dangle.js
eslint/lib/rules/comma-spacing.js
eslint/lib/rules/complexity.js
eslint/lib/rules/consistent-return.js
eslint/lib/rules/eol-last.js
eslint/lib/rules/indent-legacy.js
eslint/lib/rules/indent.js
eslint/lib/rules/lines-around-comment.js
eslint/lib/rules/max-lines-per-function.js
eslint/lib/rules/max-lines.js
eslint/lib/rules/max-params.js
eslint/lib/rules/max-statements.js
eslint/lib/rules/no-duplicate-imports.js
eslint/lib/rules/no-extra-parens.js
eslint/lib/rules/no-fallthrough.js
eslint/lib/rules/no-implicit-coercion.js
eslint/lib/rules/no-invalid-regexp.js
eslint/lib/rules/no-loss-of-precision.js
eslint/lib/rules/no-mixed-operators.js
eslint/lib/rules/no-multi-assign.js
eslint/lib/rules/no-restricted-imports.js
eslint/lib/rules/no-sequences.js
eslint/lib/rules/no-shadow.js
eslint/lib/rules/no-unused-expressions.js
eslint/lib/rules/no-unused-vars.js
eslint/lib/rules/no-useless-backreference.js
eslint/lib/rules/no-useless-computed-key.js
eslint/lib/rules/no-useless-rename.js
eslint/lib/rules/no-warning-comments.js
eslint/lib/rules/object-curly-newline.js
eslint/lib/rules/prefer-const.js
eslint/lib/rules/prefer-destructuring.js
eslint/lib/rules/radix.js
eslint/lib/rules/require-atomic-updates.js
eslint/lib/rules/space-infix-ops.js
eslint/lib/rules/spaced-comment.js
eslint/lib/rules/use-isnan.js
eslint/lib/rules/utils/ast-utils.js
eslint/lib/shared/deprecation-warnings.js
eslint/lib/shared/runtime-info.js
eslint/lib/shared/string-utils.js [new file with mode: 0644]
eslint/lib/source-code/source-code.js
eslint/lib/source-code/token-store/utils.js
eslint/messages/all-files-ignored.js [new file with mode: 0644]
eslint/messages/all-files-ignored.txt [deleted file]
eslint/messages/extend-config-missing.js [new file with mode: 0644]
eslint/messages/extend-config-missing.txt [deleted file]
eslint/messages/failed-to-read-json.js [new file with mode: 0644]
eslint/messages/failed-to-read-json.txt [deleted file]
eslint/messages/file-not-found.js [new file with mode: 0644]
eslint/messages/file-not-found.txt [deleted file]
eslint/messages/no-config-found.js [new file with mode: 0644]
eslint/messages/no-config-found.txt [deleted file]
eslint/messages/plugin-conflict.js [new file with mode: 0644]
eslint/messages/plugin-conflict.txt [deleted file]
eslint/messages/plugin-invalid.js [new file with mode: 0644]
eslint/messages/plugin-invalid.txt [deleted file]
eslint/messages/plugin-missing.js [new file with mode: 0644]
eslint/messages/plugin-missing.txt [deleted file]
eslint/messages/print-config-with-directory-path.js [new file with mode: 0644]
eslint/messages/print-config-with-directory-path.txt [deleted file]
eslint/messages/whitespace-found.js [new file with mode: 0644]
eslint/messages/whitespace-found.txt [deleted file]
eslint/package.json
eslint/tests/bench/large.js
eslint/tests/fixtures/config-extends/error.json
eslint/tests/fixtures/formatters/broken.js
eslint/tests/lib/cli-engine/cli-engine.js
eslint/tests/lib/cli-engine/file-enumerator.js
eslint/tests/lib/cli-engine/lint-result-cache.js
eslint/tests/lib/cli.js
eslint/tests/lib/eslint/eslint.js
eslint/tests/lib/init/autoconfig.js
eslint/tests/lib/init/config-initializer.js
eslint/tests/lib/linter/code-path-analysis/code-path-analyzer.js
eslint/tests/lib/linter/linter.js
eslint/tests/lib/linter/node-event-generator.js
eslint/tests/lib/linter/report-translator.js
eslint/tests/lib/rule-tester/rule-tester.js
eslint/tests/lib/rules/arrow-body-style.js
eslint/tests/lib/rules/indent.js
eslint/tests/lib/rules/no-duplicate-imports.js
eslint/tests/lib/rules/no-extend-native.js
eslint/tests/lib/rules/no-extra-parens.js
eslint/tests/lib/rules/no-implicit-coercion.js
eslint/tests/lib/rules/no-invalid-regexp.js
eslint/tests/lib/rules/no-invalid-this.js
eslint/tests/lib/rules/no-mixed-operators.js
eslint/tests/lib/rules/no-multi-assign.js
eslint/tests/lib/rules/no-redeclare.js
eslint/tests/lib/rules/no-restricted-exports.js
eslint/tests/lib/rules/no-restricted-imports.js
eslint/tests/lib/rules/no-restricted-syntax.js
eslint/tests/lib/rules/no-sequences.js
eslint/tests/lib/rules/no-shadow.js
eslint/tests/lib/rules/no-unused-expressions.js
eslint/tests/lib/rules/no-unused-vars.js
eslint/tests/lib/rules/no-useless-rename.js
eslint/tests/lib/rules/prefer-const.js
eslint/tests/lib/rules/radix.js
eslint/tests/lib/rules/require-atomic-updates.js
eslint/tests/lib/rules/space-infix-ops.js
eslint/tests/lib/shared/runtime-info.js
eslint/tests/lib/shared/string-utils.js [new file with mode: 0644]
eslint/tests/tools/loose-parser.js [deleted file]
eslint/webpack.config.js

index 48ad3776acff61a71de530e77b915118b5f9d6c2..9dbe3d05e37e5d7218470665bb41ec6a8eaa7130 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,7 @@ DSC=${PACKAGE}_${DEB_VERSION_UPSTREAM_REVISION}.dsc
 
 SRCDIR=src
 UPSTREAM=eslint
-UPSTREAMTAG=v7.18.0
+UPSTREAMTAG=v7.28.0
 BUILDSRC=${UPSTREAM}-${UPSTREAMTAG}
 
 all: ${DEB}
index 1457d9b553c05f89e8414282632af23019038568..668bc2747c54478cfe920192dd26f80650db2377 100644 (file)
@@ -71,22 +71,7 @@ module.exports = {
         "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",
-
-            // https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/issues/79
-            [
-                "filename",
-                "code",
-                "output",
-                "options",
-                "parser",
-                "parserOptions",
-                "globals",
-                "env",
-                "errors"
-            ]
-        ],
+        "eslint-plugin/test-case-property-ordering": "error",
         "eslint-plugin/test-case-shorthand-strings": "error",
         "internal-rules/multiline-comment-style": "error"
     },
index 1b1699aae0214b92cacb8f0a1cb2b98a04df1051..9b46d2944f2b2865203d12f20170800b9d7c5610 100644 (file)
@@ -2,7 +2,7 @@
 name: "\U0001F41E  Bug report"
 about: Report an issue with ESLint or rules bundled with ESLint
 title: ''
-labels: bug, triage
+labels: bug, repro:needed
 assignees: ''
 
 ---
@@ -16,7 +16,7 @@ assignees: ''
     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
+    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.
 -->
@@ -31,9 +31,12 @@ assignees: ''
 * **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>
@@ -61,7 +64,15 @@ assignees: ''
 **What did you expect to happen?**
 
 
-**What actually happened? Please include the actual, raw output from ESLint.**
+**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?**
diff --git a/eslint/.github/ISSUE_TEMPLATE/QUESTION.md b/eslint/.github/ISSUE_TEMPLATE/QUESTION.md
deleted file mode 100644 (file)
index cbcfec6..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
----
-name: "⛔ Question"
-about: Please go to https://eslint.org/chat
-title: ''
-labels: ''
-assignees: ''
-
----
-
-<!--
-    ESLint adheres to the Open JS Foundation Code of Conduct:
-    https://eslint.org/conduct
-
-    Please do not use GitHub for asking questions. Instead,
-    please visit our chatroom:
-
-        https://eslint.org/chat
-
-    Or our mailing list:
-
-        https://groups.google.com/group/eslint
-
-    Thanks in advance for helping us keep the issue tracker
-    clean!
--->
diff --git a/eslint/.github/ISSUE_TEMPLATE/SECURITY.md b/eslint/.github/ISSUE_TEMPLATE/SECURITY.md
deleted file mode 100644 (file)
index d69aa7f..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
----
-name: "⛔ Security issue"
-about: Please file security issues at https://hackerone.com/eslint
-title: ''
-labels: ''
-assignees: ''
-
----
-
-<!--
-    ESLint adheres to the Open JS Foundation Code of Conduct:
-    https://eslint.org/conduct
-
-
-    STOP! Please do not use GitHub for filing security issues.
-    Doing so is insecure and may put other ESLint users at risk.
-    To securely notify us of a security issue, please go to:
-        
-        https://hackerone.com/eslint
-
-    Thanks in advance for helping us keep the ESLint community
-    safe and secure.
--->
index 822fe546e15c5f58b573ecba899d86a296f1f0d9..af8c43451d46b506cebf286dd6635458a3744689 100644 (file)
@@ -1,5 +1,5 @@
 blank_issues_enabled: false
 contact_links:
-  - name: Breaking/Core change
-    url: https://github.com/eslint/rfcs#how-to-submit-an-rfc
-    about: Please submit an RFC here.
+  - name: 🗣 Ask a Question, Discuss
+    url: https://github.com/eslint/eslint/discussions
+    about: Get help using ESLint
index 6a57d0e6b65caf2ee59e696e30b7c422f58db0c0..f0d6c3e2ed91f4562805ea4331007c799ecfa687 100644 (file)
 
 #### What is the purpose of this pull request? (put an "X" next to an item)
 
+<!--
+    The following template is intentionally not a markdown checkbox list for the reasons
+    explained in https://github.com/eslint/eslint/pull/12848#issuecomment-580302888
+-->
+
 [ ] 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))
index f7d5bc03d80f104412335fb35997bcc2e3175424..6ba0bdc384be3e8634460e11d1cbfc358a737679 100644 (file)
@@ -26,7 +26,7 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-latest]
-        node: [15.x, 14.x, 13.x, 12.x, 10.x, "10.12.0"]
+        node: [16.x, 15.x, 14.x, 13.x, 12.x, 10.x, "10.12.0"]
         include:
         - os: windows-latest
           node: "12.x"
@@ -40,10 +40,6 @@ jobs:
         node-version: ${{ matrix.node }}
     - name: Install Packages
       run: npm install
-      if: ${{ !startswith(matrix.node, '15') }}
-    - name: Install Packages
-      run: npm install --legacy-peer-deps
-      if: ${{ startswith(matrix.node, '15') }}
     - name: Test
       run: node Makefile mocha
     - name: Fuzz Test
@@ -55,6 +51,8 @@ jobs:
     steps:
     - uses: actions/checkout@v2
     - uses: actions/setup-node@v1
+      with:
+        node-version: '12'
     - name: Install Packages
       run: npm install
     - name: Test
index e647b2d0c351bc66a54a801582f122c30aaf2287..6eaeb9a9a560a6afd9245fc6bce5cbf2edee911e 100644 (file)
@@ -1,3 +1,153 @@
+v7.28.0 - June 4, 2021
+
+* [`1237705`](https://github.com/eslint/eslint/commit/1237705dd08c209c5e3136045ec51a4ba87a3abe) Upgrade: @eslint/eslintrc to 0.4.2 (#14672) (Milos Djermanovic)
+* [`123fb86`](https://github.com/eslint/eslint/commit/123fb8648731c2c23313c544ffa1872d3024fe68) Docs: Add Feedback Needed triage description (#14670) (Nicholas C. Zakas)
+* [`c545163`](https://github.com/eslint/eslint/commit/c5451635b4e89827cfc8d8d77083647c74506e42) Update: support multiline /*eslint-env*/ directives (fixes #14652) (#14660) (薛定谔的猫)
+* [`8d1e75a`](https://github.com/eslint/eslint/commit/8d1e75a31b3e3d67130709a219bdd07ce6f3cf74) Upgrade: glob-parent version in package.json (#14658) (Hamza Najeeb)
+* [`1f048cb`](https://github.com/eslint/eslint/commit/1f048cb0eec660d2052f1758f4b2ad7b1cb424e1) Fix: no-implicit-coercion false positive with `String()` (fixes #14623) (#14641) (Milos Djermanovic)
+* [`d709abf`](https://github.com/eslint/eslint/commit/d709abfdde087325d4578b6709dc61040b8ca9d8) Chore: fix comment location in no-unused-vars (#14648) (Milos Djermanovic)
+* [`e44ce0a`](https://github.com/eslint/eslint/commit/e44ce0a8acfaad513c385150c25e76e82a1b8f12) Fix: no-duplicate-imports allow unmergeable (fixes #12758, fixes #12760) (#14238) (Soufiane Boutahlil)
+* [`bb66a3d`](https://github.com/eslint/eslint/commit/bb66a3d91af426dac9a7ffdbe47bdbbc0ffd4dd7) New: add `getPhysicalFilename()` method to rule context (fixes #11989) (#14616) (Nitin Kumar)
+* [`2e43dac`](https://github.com/eslint/eslint/commit/2e43dacd24337a82d4184fac9b44d497675f46ef) Docs: fix `no-sequences` example (#14643) (Nitin Kumar)
+* [`958ff4e`](https://github.com/eslint/eslint/commit/958ff4e8a5102f204f1484d09985e28a79790996) Docs: add note for arrow functions in no-seq rule (#14578) (Nitin Kumar)
+* [`e4f111b`](https://github.com/eslint/eslint/commit/e4f111b67d114adbf76a9c9dbb18fa4f49bc91b6) Fix: arrow-body-style crash with object pattern (fixes #14633) (#14635) (Milos Djermanovic)
+* [`ec28b5a`](https://github.com/eslint/eslint/commit/ec28b5a2bdc69f34ce29d670f5e84d2446774a00) Chore: upgrade eslint-plugin-eslint-plugin (#14590) (薛定谔的猫)
+* [`85a2725`](https://github.com/eslint/eslint/commit/85a2725b1fade5538e727102d9701ccb503e54d4) Docs: Update README team and sponsors (ESLint Jenkins)
+
+v7.27.0 - May 21, 2021
+
+* [`2c0868c`](https://github.com/eslint/eslint/commit/2c0868cbeadc9f42716fa1178ebdc6b4cee6d31e) Chore: merge all html formatter files into `html.js` (#14612) (Milos Djermanovic)
+* [`9e9b5e0`](https://github.com/eslint/eslint/commit/9e9b5e07475564813b62cd1d7562a93c5fb4bc74) Update: no-unused-vars false negative with comma operator (fixes #14325) (#14354) (Nitin Kumar)
+* [`afe9569`](https://github.com/eslint/eslint/commit/afe95693e1e4316a1c6f01d39345061d4c5921c7) Chore: use includes instead of indexOf (#14607) (Mikhail Bodrov)
+* [`c0f418e`](https://github.com/eslint/eslint/commit/c0f418e2476df98519bc156b81d20431984e8704) Chore: Remove lodash (#14287) (Stephen Wade)
+* [`52655dd`](https://github.com/eslint/eslint/commit/52655dd54925ee02af2ba3a0ebc09de959ae3101) Update: no-restricted-imports custom message for patterns (fixes #11843) (#14580) (Alex Holden)
+* [`967b1c4`](https://github.com/eslint/eslint/commit/967b1c4ceca8f5248378477da94ff118dafaa647) Chore: Fix typo in large.js (#14589) (Ikko Ashimine)
+* [`2466a05`](https://github.com/eslint/eslint/commit/2466a05160de60958457d984b79fd445c12ebc98) Sponsors: Sync README with website (ESLint Jenkins)
+* [`fe29f18`](https://github.com/eslint/eslint/commit/fe29f18227fd02fd7c3da033417d621275b00d0a) Sponsors: Sync README with website (ESLint Jenkins)
+* [`086c1d6`](https://github.com/eslint/eslint/commit/086c1d6e8593cf8e7851daa8f2a890c213cf6999) Chore: add more test cases for `no-sequences` (#14579) (Nitin Kumar)
+* [`6a2ced8`](https://github.com/eslint/eslint/commit/6a2ced892c0dc43fa4942293b9f1c4b9151c3741) Docs: Update README team and sponsors (ESLint Jenkins)
+
+v7.26.0 - May 7, 2021
+
+* [`aaf65e6`](https://github.com/eslint/eslint/commit/aaf65e629adb74401092c3ccc9cb4e4bd1c8609b) Upgrade: eslintrc for ModuleResolver fix (#14577) (Brandon Mills)
+* [`ae6dbd1`](https://github.com/eslint/eslint/commit/ae6dbd148aaca83e4bd04b9351b54029c50fac8a) Fix: track variables, not names in require-atomic-updates (fixes #14208) (#14282) (Patrick Ahmetovic)
+* [`6a86e50`](https://github.com/eslint/eslint/commit/6a86e5018a3733049c09261bcabae422fbea893d) Chore: remove loose-parser tests (fixes #14315) (#14569) (Milos Djermanovic)
+* [`ee3a3ea`](https://github.com/eslint/eslint/commit/ee3a3ead893d185cc4b1ae9041940cb0968767e1) Fix: create `.eslintrc.cjs` for `module` type (#14304) (Nitin Kumar)
+* [`6791dec`](https://github.com/eslint/eslint/commit/6791decfc58b7b09cfd0aabd15a3d14148aae073) Docs: fix example for require-atomic-updates (#14562) (Milos Djermanovic)
+* [`388eb7e`](https://github.com/eslint/eslint/commit/388eb7e14039b8951462b311d6121002ca5232cb) Sponsors: Sync README with website (ESLint Jenkins)
+* [`f071d1e`](https://github.com/eslint/eslint/commit/f071d1ef91286bf2e3fb63d9b679ff7702819a1e) Update: Add automated suggestion to `radix` rule for parsing decimals (#14291) (Bryan Mishkin)
+* [`0b6a3f3`](https://github.com/eslint/eslint/commit/0b6a3f31e6e78825114f82d4e0aed9cd72f784ac) New: Include XO style guide in `eslint --init` (#14193) (Federico Brigante)
+
+v7.25.0 - April 23, 2021
+
+* [`5df5e4a`](https://github.com/eslint/eslint/commit/5df5e4a9976964fcf4dc67e241d4e22ec1370fe0) Update: highlight last write reference for no-unused-vars (fixes #14324) (#14335) (Nitin Kumar)
+* [`0023872`](https://github.com/eslint/eslint/commit/00238729329b86b4f8af89ebfe278da3095a6075) Docs: Add deprecated note to `working-with-rules-deprecated` page (#14344) (Michael Novotny)
+* [`36fca70`](https://github.com/eslint/eslint/commit/36fca70fa29ab65080076810de98e09133254b8a) Chore: Upgrade eslump to 3.0.0 (#14350) (Stephen Wade)
+* [`59b689a`](https://github.com/eslint/eslint/commit/59b689a0b3fa658b8380431007cc1facb4617a3b) Chore: add node v16 (#14355) (薛定谔的猫)
+* [`35a1f5e`](https://github.com/eslint/eslint/commit/35a1f5e967e4e87360d3e70d3fca0f7adeeaa1d7) Sponsors: Sync README with website (ESLint Jenkins)
+* [`fb0a92b`](https://github.com/eslint/eslint/commit/fb0a92b3d2fed4a17bc39b7f02c540cd1175ec7d) Chore: rename misspelled identifier in test (#14346) (Tobias Nießen)
+* [`f2babb1`](https://github.com/eslint/eslint/commit/f2babb1069194166e0ac1afd1269bbd06ac299b6) Docs: update pull request template (#14336) (Nitin Kumar)
+* [`02dde29`](https://github.com/eslint/eslint/commit/02dde29eeb523ca24bc4ae7797d38627c3ba9fe9) Docs: Fix anchor in 'docs/developer-guide/working-with-rules.md' (#14332) (Nate-Wilkins)
+* [`07d14c3`](https://github.com/eslint/eslint/commit/07d14c304c358fbc9c3d318e1377d2b2bda9179f) Chore: remove extraneous command from lint-staged config (#14314) (James George)
+* [`41b3570`](https://github.com/eslint/eslint/commit/41b3570b6c014c534bb3208ed00050fd99842101) Update: lint code block with same extension but different content (#14227) (JounQin)
+* [`eb29996`](https://github.com/eslint/eslint/commit/eb299966bdc3920dd2c6f9774d95103d242fc409) Docs: add more examples with arrow functions for no-sequences rule (#14313) (Nitin Kumar)
+
+v7.24.0 - April 9, 2021
+
+* [`0c346c8`](https://github.com/eslint/eslint/commit/0c346c87fa83c6d1184fdafb9c0748c2e15a423d) Chore: ignore `pnpm-lock.yaml` (#14303) (Nitin Kumar)
+* [`f06ecdf`](https://github.com/eslint/eslint/commit/f06ecdf78b6d6f366434d73a6acfe7041d575223) Update: Add disallowTemplateShorthand option in no-implicit-coercion (#13579) (Remco Haszing)
+* [`71a80e3`](https://github.com/eslint/eslint/commit/71a80e38aab2dada01b808ed43d9b0e806d863c4) Docs: fix broken links in Node.js API docs toc (#14296) (u-sho (Shouhei Uechi))
+* [`bd46dc4`](https://github.com/eslint/eslint/commit/bd46dc4647faa4c3bbb5f60d4c00616a64081398) Docs: Fix incorrect reference to "braces" in arrow-parens (#14300) (emclain)
+* [`0d6235e`](https://github.com/eslint/eslint/commit/0d6235ea201b8b90761ee69bb4d46ae18899c28d) Docs: update header in max-lines (#14273) (Shinigami)
+* [`70c9216`](https://github.com/eslint/eslint/commit/70c92164017238e329e3a2d1654a0227b8f953f7) Docs: Update issue triage to include blocked column (#14275) (Nicholas C. Zakas)
+* [`abca186`](https://github.com/eslint/eslint/commit/abca186a845200fd7728c4e5f220973e640054f9) Docs: Fix typo in suggestions section (#14293) (Kevin Partington)
+* [`c4d8b0d`](https://github.com/eslint/eslint/commit/c4d8b0db62b859e721105d4bc0f4044ce346995e) Fix: no-unused-vars ignoreRestSiblings check assignments (fixes #14163) (#14264) (YeonJuan)
+* [`b51d077`](https://github.com/eslint/eslint/commit/b51d0778d76c2aa27578caca3ea82c867dced3e4) Update: add ignoreNonDeclaration to no-multi-assign rule (fixes #12545) (#14185) (t-mangoe)
+* [`c981fb1`](https://github.com/eslint/eslint/commit/c981fb1994cd04914042ced1980aa86b68ba7be9) Chore: Upgrade mocha to 8.3.2 (#14278) (Stephen Wade)
+* [`147fc04`](https://github.com/eslint/eslint/commit/147fc045e699811fab33dddf77498324ddf7e9d6) Docs: Fix `repro:needed` label in bug report template (#14285) (Milos Djermanovic)
+* [`e1cfde9`](https://github.com/eslint/eslint/commit/e1cfde93eec71a15c2df1ad660a7a6171204ba80) Docs: Update bug report template (#14276) (Nicholas C. Zakas)
+* [`c85c2f1`](https://github.com/eslint/eslint/commit/c85c2f1138a9e952655f19ee780ab0c8e35431a8) Docs: Add fatal to Node.js API LintMessage type (#14251) (Brandon Mills)
+
+v7.23.0 - March 26, 2021
+
+* [`687ccae`](https://github.com/eslint/eslint/commit/687ccae517b8b815cf21e948f80d22e2bf118a99) Update: add option "allowInParentheses" to no-sequences (fixes #14197) (#14199) (Daniel Rentz)
+* [`dbf2529`](https://github.com/eslint/eslint/commit/dbf252964d9a2b8957cfe0aed5c87a6d4a5cce24) Sponsors: Sync README with website (ESLint Jenkins)
+* [`4bdf2c1`](https://github.com/eslint/eslint/commit/4bdf2c1dade27625b601080687ce95b8c229e491) Sponsors: Sync README with website (ESLint Jenkins)
+* [`49d1697`](https://github.com/eslint/eslint/commit/49d16977d969070e5240074e76036f56631a90d3) Chore: Upgrade eslint-plugin-jsdoc to v25 and remove --legacy-peer-deps (#14244) (Brandon Mills)
+* [`43f1685`](https://github.com/eslint/eslint/commit/43f1685356b9840e09631843ad9ccf0440a498b0) Update: `--quiet` should not supress `--max-warnings` (fixes #14202) (#14242) (Milos Djermanovic)
+* [`909c727`](https://github.com/eslint/eslint/commit/909c7271b8d294bd884827ad5df02615b6ec5e82) Docs: Add valid example that shows vars in a block scope (#14230) (Ed S)
+* [`28583eb`](https://github.com/eslint/eslint/commit/28583eb8ada20f32579841bec3fbd60a018d5931) Fix: no-mixed-operators false positives with `? :` (fixes #14223) (#14226) (Milos Djermanovic)
+* [`a99eb2d`](https://github.com/eslint/eslint/commit/a99eb2dc2a297d16e40a9feef3956668716c4eb5) Fix: Clarify line breaks in object-curly-newline (fixes #14024) (#14063) (armin yahya)
+* [`8984c91`](https://github.com/eslint/eslint/commit/8984c91372e64d1e8dd2ce21b87b80977d57bff9) Update: eslint --env-info output os info (#14059) (薛定谔的猫)
+* [`2a79306`](https://github.com/eslint/eslint/commit/2a79306f71c4c80f1e3e73be2a140d07cf55c63d) Sponsors: Sync README with website (ESLint Jenkins)
+* [`ebd7026`](https://github.com/eslint/eslint/commit/ebd70263f6e6fe597613d90f4b8de84710c2f3d6) Docs: Fix typo (#14225) (Greg Finley)
+* [`a2013fc`](https://github.com/eslint/eslint/commit/a2013fcf996c8651bc760df21d900442828a6884) Sponsors: Sync README with website (ESLint Jenkins)
+
+v7.22.0 - March 12, 2021
+
+* [`3a432d8`](https://github.com/eslint/eslint/commit/3a432d82b3a5710aff7da20302fe0b94fedc46c2) Docs: Improve documentation for indent rule (#14168) (Serkan Özel)
+* [`f62ec8d`](https://github.com/eslint/eslint/commit/f62ec8d30d925e70e4d0d40640857c587ac2e116) Update: throw error when fix range is invalid (#14142) (Jacob Bandes-Storch)
+* [`0eecad2`](https://github.com/eslint/eslint/commit/0eecad271358f753730741fcfcb2f7cc915c1fa7) Upgrade: Update lodash in package.json to V 4.17.21 (#14159) (Basem Al-Nabulsi)
+* [`5ad91aa`](https://github.com/eslint/eslint/commit/5ad91aa7df3d6bc185786e6eccd9e055fd951055) Update: report es2021 globals in no-extend-native (refs #13602) (#14177) (Milos Djermanovic)
+* [`c295581`](https://github.com/eslint/eslint/commit/c295581aca4e08ec4ae8e5ee5726a6f454a3ee26) Chore: remove leftover JSDoc from lint-result-cache (#14176) (Milos Djermanovic)
+* [`0d541f9`](https://github.com/eslint/eslint/commit/0d541f9d9d58966372e2055a8f69fb9483d56a4b) Chore: Reduce lodash usage (#14178) (Stephen Wade)
+* [`27a67d7`](https://github.com/eslint/eslint/commit/27a67d71ffa9bbd7af02ae448844e127bcf956dc) Sponsors: Sync README with website (ESLint Jenkins)
+* [`459d821`](https://github.com/eslint/eslint/commit/459d821f4a599501ceb002f9d7a5034fc45ffbb0) Chore: upgrade dependencies of browser test (#14127) (Pig Fang)
+* [`ebfb63a`](https://github.com/eslint/eslint/commit/ebfb63a682004a008f2707dbad616e5ae1630b2c) Sponsors: Sync README with website (ESLint Jenkins)
+* [`3ba029f`](https://github.com/eslint/eslint/commit/3ba029fbffd44068be93254890fc2aec3e92c212) Docs: Remove Extraneous Dash (#14164) (Danny Hurlburt)
+* [`6f4540e`](https://github.com/eslint/eslint/commit/6f4540ea7ea39775906526506fd7abd7ea97610c) Sponsors: Sync README with website (ESLint Jenkins)
+* [`ddf361c`](https://github.com/eslint/eslint/commit/ddf361ca2a2a01a9974f421e5f62270df282d0e8) Docs: Fix Formatting (#14154) (Danny Hurlburt)
+* [`c0d2ac1`](https://github.com/eslint/eslint/commit/c0d2ac16f8f9c75c62c78e9fe6a24a25ba0d7828) Sponsors: Sync README with website (ESLint Jenkins)
+* [`a8df03e`](https://github.com/eslint/eslint/commit/a8df03efe3bc47665d2112c2cdd5bead337d475d) Docs: Clarify triage process (#14117) (Nicholas C. Zakas)
+
+v7.21.0 - February 27, 2021
+
+* [`3cd5440`](https://github.com/eslint/eslint/commit/3cd5440b94d5fa4f11a09f50b685f6150f0c2d41) Upgrade: @eslint/eslintrc to 0.4.0 (#14147) (Brandon Mills)
+* [`c0b8c71`](https://github.com/eslint/eslint/commit/c0b8c71df4d0b3f54b20587432d9133741985d5c) Upgrade: Puppeteer to 7.1.0 (#14122) (Tim van der Lippe)
+* [`08ae31e`](https://github.com/eslint/eslint/commit/08ae31e539e381cd0eabf6393fa5c20f1d59125f) New: Implement cacheStrategy (refs eslint/rfcs#63) (#14119) (Manu Chambon)
+* [`5e51fd2`](https://github.com/eslint/eslint/commit/5e51fd28dc773c11c924450d24088f97f2824f00) Update: do not ignore symbolic links (fixes #13551, fixes #13615) (#14126) (Pig Fang)
+* [`87c43a5`](https://github.com/eslint/eslint/commit/87c43a5d7ea2018cffd6d9b5c431ecb60caaf0d6) Chore: improve a few comments and fix typos (#14125) (Tobias Nießen)
+* [`e19c51e`](https://github.com/eslint/eslint/commit/e19c51ea2ef2cf666d94218c66f6b223bb2e9dae) Sponsors: Sync README with website (ESLint Jenkins)
+* [`b8aea99`](https://github.com/eslint/eslint/commit/b8aea9988b6451b6a05af4f3ede8d6ed5c1d9926) Fix: pluralize 'line' to 'lines' in max-lines-per-function description (#14115) (Trevin Hofmann)
+* [`f5b53e2`](https://github.com/eslint/eslint/commit/f5b53e285ab5286ea382d73b379b820f649c20d0) Sponsors: Sync README with website (ESLint Jenkins)
+* [`eee1213`](https://github.com/eslint/eslint/commit/eee12134ce0956e9f825c1630776b1731551a948) Sponsors: Sync README with website (ESLint Jenkins)
+* [`5c4d7ea`](https://github.com/eslint/eslint/commit/5c4d7ea8d2e8d137c42b918dc99a84b4a5fed9b3) Sponsors: Sync README with website (ESLint Jenkins)
+
+v7.20.0 - February 12, 2021
+
+* [`f4ac3b0`](https://github.com/eslint/eslint/commit/f4ac3b0e7072fbd3c14e9c64ff0c2c255a4eb730) Docs: fix sibling selector descriptions (#14099) (Milos Djermanovic)
+* [`9d6063a`](https://github.com/eslint/eslint/commit/9d6063add931f0803cae1676d5df307baf114360) Fix: Crash with esquery when using JSX (fixes #13639) (#14072) (Yosuke Ota)
+* [`a0871f1`](https://github.com/eslint/eslint/commit/a0871f1840060bd23cfe0952a096b107142db2f0) Docs: Triage process (#14014) (Nicholas C. Zakas)
+* [`ad90761`](https://github.com/eslint/eslint/commit/ad9076183bc2c2029525edfc4596e403999348d1) Update: add enforceForJSX option to no-unused-expressions rule (#14012) (Duncan Beevers)
+* [`d6c84af`](https://github.com/eslint/eslint/commit/d6c84af67318537177ffac0120a81af08e3e9df4) Fix: `--init` autoconfig shouldn't add deprecated rules (fixes #14017) (#14060) (Milos Djermanovic)
+* [`9b277a1`](https://github.com/eslint/eslint/commit/9b277a16a7261e51b7ba36d6de7f996e9203a6a4) Fix: Support ENOTDIR error code in the folder existence checking utility (#13973) (Constantine Genchevsky)
+* [`7aeb127`](https://github.com/eslint/eslint/commit/7aeb12798f2b9da706f3593f26a02e717929c9af) Upgrade: pin @babel/code-frame@7.12.11 (#14067) (Milos Djermanovic)
+* [`b4e2af5`](https://github.com/eslint/eslint/commit/b4e2af5db1c29343ffec2cd104b04bf39b77ee56) Docs: Add more fields to bug report template (#14039) (Nicholas C. Zakas)
+* [`96f1d49`](https://github.com/eslint/eslint/commit/96f1d49a4647e59f2fb918be096654e290513adc) Sponsors: Sync README with website (ESLint Jenkins)
+* [`cb27b0a`](https://github.com/eslint/eslint/commit/cb27b0abeda6dfee55dd43b9cbe12afad321f55d) Build: package.json update for eslint-config-eslint release (ESLint Jenkins)
+* [`4cab165`](https://github.com/eslint/eslint/commit/4cab165bf4e6e5e9f42a59a37a8ff2548c0af87d) Sponsors: Sync README with website (ESLint Jenkins)
+
+v7.19.0 - January 30, 2021
+
+* [`ce7f061`](https://github.com/eslint/eslint/commit/ce7f06121d9eb9cc2b3da24b4456b4d382e1413b) Update: add shadowed variable loc to message in no-shadow (fixes #13646) (#13841) (t-mangoe)
+* [`c60e23f`](https://github.com/eslint/eslint/commit/c60e23ff306a14ca6eabcadb275ed27995fcc6e4) Update: fix `let` logic in for-in and for-of loops in no-extra-parens (#14011) (Milos Djermanovic)
+* [`d76e8f6`](https://github.com/eslint/eslint/commit/d76e8f69bd791357c67ada7b5c55608acf29b622) Fix: no-useless-rename invalid autofix with parenthesized identifiers (#14032) (Milos Djermanovic)
+* [`5800d92`](https://github.com/eslint/eslint/commit/5800d921144ec330b6ee7cd03364434007331354) Docs: Clarify stylistic rule update policy (#14052) (Brandon Mills)
+* [`0ccf6d2`](https://github.com/eslint/eslint/commit/0ccf6d200147437b338cadb34546451972befd75) Docs: remove configuring.md (#14036) (Milos Djermanovic)
+* [`65bb0ab`](https://github.com/eslint/eslint/commit/65bb0abde56f72586036fff151aa2d13f1b7be6c) Chore: Clean up new issue workflow (#14040) (Nicholas C. Zakas)
+* [`e1da90f`](https://github.com/eslint/eslint/commit/e1da90fc414a3c9c16f52db4a5bd81bd4f9532a4) Fix: nested indenting for offsetTernaryExpressions: true (fixes #13971) (#13972) (Chris Brody)
+* [`1a078b9`](https://github.com/eslint/eslint/commit/1a078b9166f29cb3760435ddbc1a0da4a0974d4a) Update: check ternary `:` even if `?` was reported in space-infix-ops (#13963) (Milos Djermanovic)
+* [`fb27422`](https://github.com/eslint/eslint/commit/fb274226242eaebc1480fc9c901202986afc3c8a) Fix: extend prefer-const fixer range to whole declaration (fixes #13899) (#14033) (Nitin Kumar)
+* [`e0b05c7`](https://github.com/eslint/eslint/commit/e0b05c704f3ce6f549d14718236d22fe49fcb611) Docs: add a correct example to no-unsafe-optional-chaining (refs #14029) (#14050) (armin yahya)
+* [`46e836d`](https://github.com/eslint/eslint/commit/46e836d46442d2ec756038a2e12ba19b74394dbd) Sponsors: Sync README with website (ESLint Jenkins)
+* [`3fc4fa4`](https://github.com/eslint/eslint/commit/3fc4fa485ca9ccd5e16dbc7e53ba31452d22dc4a) Docs: update configuring links (#14038) (Milos Djermanovic)
+* [`8561c21`](https://github.com/eslint/eslint/commit/8561c2116ef89e53ebffb750066f1b00a4acdb76) Docs: fix broken links in configuring/README.md (#14046) (Milos Djermanovic)
+* [`1c309eb`](https://github.com/eslint/eslint/commit/1c309ebca4a81a0faf397103dbc621019dea8c9c) Update: fix no-invalid-regexp false negatives with no flags specified (#14018) (Milos Djermanovic)
+* [`f6602d5`](https://github.com/eslint/eslint/commit/f6602d569427e9e2a4f3b5ca3fc3a8bffb28d15e) Docs: Reorganize Configuration Documentation (#13837) (klkhan)
+* [`c753b44`](https://github.com/eslint/eslint/commit/c753b442ef67867a178ffc2ad29b4e0534f72469) Sponsors: Sync README with website (ESLint Jenkins)
+* [`a4fdb70`](https://github.com/eslint/eslint/commit/a4fdb7001aa41b9ad8bb92cc8a47b9135c94afc7) Docs: Fixed Typo (#14007) (Yash Singh)
+* [`f7ca481`](https://github.com/eslint/eslint/commit/f7ca48165d025e01c38698352cff24d1de87cc8b) Docs: Explain why we disable lock files (refs eslint/tsc-meetings#234) (#14006) (Brandon Mills)
+
 v7.18.0 - January 15, 2021
 
 * [`e3264b2`](https://github.com/eslint/eslint/commit/e3264b26a625d926a1ea96df1c4b643af5c3797c) Upgrade: @eslint/eslintrc to improve error message for invalid extends (#14009) (Milos Djermanovic)
index 840a51a86d7253f0a3be10fe25efdc46aecc6fbd..6cffb770c640c3abe9bcaf275f749bb31950f43c 100644 (file)
@@ -13,8 +13,7 @@
 
 require("shelljs/make");
 
-const lodash = require("lodash"),
-    checker = require("npm-license"),
+const checker = require("npm-license"),
     ReleaseOps = require("eslint-release"),
     dateformat = require("dateformat"),
     fs = require("fs"),
@@ -200,7 +199,7 @@ function generateRuleIndexPage() {
                         recommended: rule.meta.docs.recommended || false,
                         fixable: !!rule.meta.fixable
                     },
-                    category = lodash.find(categoriesData.categories, { name: rule.meta.docs.category });
+                    category = categoriesData.categories.find(c => c.name === rule.meta.docs.category);
 
                 if (!category.rules) {
                     category.rules = [];
@@ -487,7 +486,7 @@ target.lint = function([fix = false] = []) {
     }
 
     echo("Validating JSON Files");
-    lodash.forEach(JSON_FILES, validateJsonFile);
+    JSON_FILES.forEach(validateJsonFile);
 
     echo("Validating Markdown Files");
     lastReturn = lintMarkdown(MARKDOWN_FILES_ARRAY);
index 73a30990e509740575ce534ff2a6b7a631ae14ba..438a087ff234326811277bc7771b031a76ab84dc 100644 (file)
@@ -35,10 +35,11 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J
 6. [Releases](#releases)
 7. [Security Policy](#security-policy)
 8. [Semantic Versioning Policy](#semantic-versioning-policy)
-9. [License](#license)
-10. [Team](#team)
-11. [Sponsors](#sponsors)
-12. [Technology Sponsors](#technology-sponsors)
+9. [Stylistic Rule Updates](#stylistic-rule-updates)
+10. [License](#license)
+11. [Team](#team)
+12. [Sponsors](#sponsors)
+13. [Technology Sponsors](#technology-sponsors)
 
 ## <a name="installation-and-usage"></a>Installation and Usage
 
@@ -136,6 +137,16 @@ Once a language feature has been adopted into the ECMAScript standard (stage 4 a
 
 Join our [Mailing List](https://groups.google.com/group/eslint) or [Chatroom](https://eslint.org/chat).
 
+### Why doesn't ESLint lock dependency versions?
+
+Lock files like `package-lock.json` are helpful for deployed applications. They ensure that dependencies are consistent between environments and across deployments.
+
+Packages like `eslint` that get published to the npm registry do not include lock files. `npm install eslint` as a user will respect version constraints in ESLint's `package.json`. ESLint and its dependencies will be included in the user's lock file if one exists, but ESLint's own lock file would not be used.
+
+We intentionally don't lock dependency versions so that we have the latest compatible dependency versions in development and CI that our users get when installing ESLint in a project.
+
+The Twilio blog has a [deeper dive](https://www.twilio.com/blog/lockfiles-nodejs) to learn more.
+
 ## <a name="releases"></a>Releases
 
 We have scheduled releases every two weeks on Friday or Saturday. You can follow a [release issue](https://github.com/eslint/eslint/issues?q=is%3Aopen+is%3Aissue+label%3Arelease) for updates about the scheduling of any particular release.
@@ -177,6 +188,15 @@ ESLint follows [semantic versioning](https://semver.org). However, due to the na
 
 According to our policy, any minor update may report more linting errors than the previous release (ex: from a bug fix). As such, we recommend using the tilde (`~`) in `package.json` e.g. `"eslint": "~3.1.0"` to guarantee the results of your builds.
 
+## <a name="stylistic-rule-updates"></a>Stylistic Rule Updates
+
+Stylistic rules are frozen according to [our policy](https://eslint.org/blog/2020/05/changes-to-rules-policies) on how we evaluate new rules and rule changes.
+This means:
+
+* **Bug fixes**: We will still fix bugs in stylistic rules.
+* **New ECMAScript features**: We will also make sure stylistic rules are compatible with new ECMAScript features.
+* **New options**: We will **not** add any new options to stylistic rules unless an option is the only way to fix a bug or support a newly-added ECMAScript feature.
+
 ## <a name="license"></a>License
 
 [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint.svg?type=large)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint?ref=badge_large)
@@ -203,11 +223,6 @@ Nicholas C. Zakas
 Brandon Mills
 </a>
 </td><td align="center" valign="top" width="11%">
-<a href="https://github.com/mysticatea">
-<img src="https://github.com/mysticatea.png?s=75" width="75" height="75"><br />
-Toru Nagashima
-</a>
-</td><td align="center" valign="top" width="11%">
 <a href="https://github.com/mdjermanovic">
 <img src="https://github.com/mdjermanovic.png?s=75" width="75" height="75"><br />
 Milos Djermanovic
@@ -220,6 +235,11 @@ Milos Djermanovic
 The people who review and implement new features.
 
 <table><tbody><tr><td align="center" valign="top" width="11%">
+<a href="https://github.com/mysticatea">
+<img src="https://github.com/mysticatea.png?s=75" width="75" height="75"><br />
+Toru Nagashima
+</a>
+</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 />
 薛定谔的猫
@@ -260,10 +280,10 @@ The following companies, organizations, and individuals support ESLint's ongoing
 <!-- NOTE: This section is autogenerated. Do not manually edit.-->
 <!--sponsorsstart-->
 <h3>Platinum Sponsors</h3>
-<p><a href="https://automattic.com"><img src="https://images.opencollective.com/photomatt/ff91f0b/logo.png" alt="Automattic" height="undefined"></a></p><h3>Gold Sponsors</h3>
-<p><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.shopify.com"><img src="https://images.opencollective.com/shopify/e780cd4/logo.png" alt="Shopify" 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://aka.ms/microsoftfossfund"><img src="https://avatars1.githubusercontent.com/u/67931232?u=7fddc652a464d7151b97e8f108392af7d54fa3e8&v=4" alt="Microsoft FOSS Fund Sponsorships" 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> <a href="https://www.ampproject.org/"><img src="https://images.opencollective.com/amp/c8a3b25/logo.png" alt="AMP Project" height="64"></a></p><h3>Bronze Sponsors</h3>
-<p><a href="https://streamat.se"><img src="https://images.opencollective.com/streamat/46890db/logo.png" alt="Streamat" height="32"></a> <a href="https://thestandarddaily.com/"><img src="https://images.opencollective.com/eric-watson/db4e598/avatar.png" alt="The Standard Daily" height="32"></a> <a href="https://writersperhour.com"><img src="https://images.opencollective.com/writersperhour/5787d4b/logo.png" alt="Writers Per Hour" height="32"></a> <a href="https://www.betacalendars.com/february-calendar.html"><img src="https://images.opencollective.com/betacalendars/9334b33/logo.png" alt="February 2021 calendar" height="32"></a> <a href="https://buy.fineproxy.org/eng/"><img src="https://images.opencollective.com/buy-fineproxy-org/b282e39/logo.png" alt="Buy.Fineproxy.Org" 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="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://discordapp.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://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>
 <!--sponsorsend-->
 
 ## <a name="technology-sponsors"></a>Technology Sponsors
index 86291b0f52750acbde7f875ebacbb5c9fd961f33..5fa5766828edc3f234f805af6dd2b0fe5989445c 100755 (executable)
@@ -66,11 +66,8 @@ function readStdin() {
  */
 function getErrorMessage(error) {
 
-    // Lazy loading because those are used only if error happened.
-    const fs = require("fs");
-    const path = require("path");
+    // Lazy loading because this is used only if an error happened.
     const util = require("util");
-    const lodash = require("lodash");
 
     // Foolproof -- thirdparty module might throw non-object.
     if (typeof error !== "object" || error === null) {
@@ -80,14 +77,7 @@ function getErrorMessage(error) {
     // Use templates if `error.messageTemplate` is present.
     if (typeof error.messageTemplate === "string") {
         try {
-            const templateFilePath = path.resolve(
-                __dirname,
-                `../messages/${error.messageTemplate}.txt`
-            );
-
-            // Use sync API because Node.js should exit at this tick.
-            const templateText = fs.readFileSync(templateFilePath, "utf-8");
-            const template = lodash.template(templateText);
+            const template = require(`../messages/${error.messageTemplate}.js`);
 
             return template(error.messageData || {});
         } catch {
index e09a829d17cee292f804b00a2110e97d36a98071..dad03d89e93da3a845c7380c729b229a2e9a006a 100644 (file)
@@ -24,6 +24,7 @@ module.exports = {
      */
     cacheLocation: "",
     cacheFile: ".eslintcache",
+    cacheStrategy: "metadata",
     fix: false,
     allowInlineConfig: true,
     reportUnusedDisableDirectives: void 0,
index 7762bc5d65273f8779ca4091a9a7f0d3a944eb1c..cd3fc29365adc18ce7030b3b8a47e036cad9ff5a 100644 (file)
@@ -195,8 +195,6 @@ bar();
 ### To check whether or not this is reachable
 
 ```js
-var last = require("lodash").last;
-
 function isReachable(segment) {
     return segment.reachable;
 }
@@ -215,7 +213,7 @@ module.exports = function(context) {
 
         // Checks reachable or not.
         "ExpressionStatement": function(node) {
-            var codePath = last(codePathStack);
+            var codePath = codePathStack[codePathStack.length - 1];
 
             // Checks the current code path segments.
             if (!codePath.currentSegments.some(isReachable)) {
@@ -239,8 +237,6 @@ So a rule must not modify those instances.
 Please use a map of information instead.
 
 ```js
-var last = require("lodash").last;
-
 function hasCb(node, context) {
     if (node.type.indexOf("Function") !== -1) {
         return context.getDeclaredVariables(node).some(function(v) {
@@ -285,8 +281,10 @@ module.exports = function(context) {
 
         // Manages state of code paths.
         "onCodePathSegmentStart": function(segment) {
+            var funcInfo = funcInfoStack[funcInfoStack.length - 1];
+
             // Ignores if `cb` doesn't exist.
-            if (!last(funcInfoStack).hasCb) {
+            if (!funcInfo.hasCb) {
                 return;
             }
 
@@ -304,7 +302,7 @@ module.exports = function(context) {
 
         // Checks reachable or not.
         "CallExpression": function(node) {
-            var funcInfo = last(funcInfoStack);
+            var funcInfo = funcInfoStack[funcInfoStack.length - 1];
 
             // Ignores if `cb` doesn't exist.
             if (!funcInfo.hasCb) {
index c283d51bf91507ab272746de94e525b976326403..1c84b2e1f73b969e02b74a30d2b3dbc9ea45ebca 100644 (file)
@@ -195,8 +195,6 @@ bar();
 ### To check whether or not this is reachable
 
 ```js
-var last = require("lodash").last;
-
 function isReachable(segment) {
     return segment.reachable;
 }
@@ -215,7 +213,7 @@ module.exports = function(context) {
 
         // Checks reachable or not.
         "ExpressionStatement": function(node) {
-            var codePath = last(codePathStack);
+            var codePath = codePathStack[codePathStack.length - 1];
 
             // Checks the current code path segments.
             if (!codePath.currentSegments.some(isReachable)) {
@@ -239,8 +237,6 @@ So a rule must not modify those instances.
 Please use a map of information instead.
 
 ```js
-var last = require("lodash").last;
-
 function hasCb(node, context) {
     if (node.type.indexOf("Function") !== -1) {
         return context.getDeclaredVariables(node).some(function(v) {
@@ -285,8 +281,10 @@ module.exports = function(context) {
 
         // Manages state of code paths.
         "onCodePathSegmentStart": function(segment) {
+            var funcInfo = funcInfoStack[funcInfoStack - 1];
+
             // Ignores if `cb` doesn't exist.
-            if (!last(funcInfoStack).hasCb) {
+            if (!funcInfo.hasCb) {
                 return;
             }
 
@@ -304,7 +302,7 @@ module.exports = function(context) {
 
         // Checks reachable or not.
         "CallExpression": function(node) {
-            var funcInfo = last(funcInfoStack);
+            var funcInfo = funcInfoStack[funcInfoStack - 1];
 
             // Ignores if `cb` doesn't exist.
             if (!funcInfo.hasCb) {
index cfa03721876243c9b444a4ce7d4d37d0259c3a0a..f1e21584e326c3b9f444db7f5d12a90a9c9959b6 100644 (file)
@@ -21,15 +21,15 @@ While ESLint is designed to be run on the command line, it's possible to use ESL
     * [EditInfo type][editinfo]
     * [Formatter type][formatter]
 * [SourceCode](#sourcecode)
-    * [splitLines()](#sourcecode-splitlines)
+    * [splitLines()](#sourcecodesplitlines)
 * [Linter](#linter)
-    * [verify()](#linter-verify)
-    * [verifyAndFix()](#linter-verifyandfix)
-    * [defineRule()](#linter-definerule)
-    * [defineRules()](#linter-definerules)
-    * [getRules()](#linter-getrules)
-    * [defineParser()](#linter-defineparser)
-    * [version](#linter-version)
+    * [verify()](#linterverify)
+    * [verifyAndFix()](#linterverifyandfix)
+    * [defineRule()](#linterdefinerule)
+    * [defineRules()](#linterdefinerules)
+    * [getRules()](#lintergetrules)
+    * [defineParser()](#linterdefineparser)
+    * [version](#linterversionlinterversion)
 * [linter (deprecated)](#linter-1)
 * [CLIEngine (deprecated)](#cliengine)
 * [RuleTester](#ruletester)
@@ -156,6 +156,8 @@ The `ESLint` constructor takes an `options` object. If you omit the `options` ob
   Default is `false`. If `true` is present, the [`eslint.lintFiles()`][eslint-lintfiles] method caches lint results and uses it if each target file is not changed. Please mind that ESLint doesn't clear the cache when you upgrade ESLint plugins. In that case, you have to remove the cache file manually. The [`eslint.lintText()`][eslint-linttext] method doesn't use caches even if you pass the `options.filePath` to the method.
 * `options.cacheLocation` (`string`)<br>
   Default is `.eslintcache`. The [`eslint.lintFiles()`][eslint-lintfiles] method writes caches into this file.
+* `options.cacheStrategy` (`string`)<br>
+  Default is `"metadata"`. Strategy for the cache to use for detecting changed files. Can be either `"metadata"` or `"content"`.
 
 ### ◆ eslint.lintFiles(patterns)
 
@@ -351,6 +353,8 @@ The `LintMessage` value is the information of each linting error. The `messages`
   The rule name that generates this lint message. If this message is generated by the ESLint core rather than rules, this is `null`.
 * `severity` (`1 | 2`)<br>
   The severity of this message. `1` means warning and `2` means error.
+* `fatal` (`boolean | 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>
index ac1ae0d3f42ecbe6d216bb9621a7a42c14c0848b..afe6600dc8c2b54104102f83e7ed3cbc8b2c403c 100644 (file)
@@ -40,8 +40,8 @@ The following selectors are supported:
 * nth-last-child (no ax+b support): `:nth-last-child(1)`
 * descendant: `FunctionExpression ReturnStatement`
 * child: `UnaryExpression > Literal`
-* following sibling: `ArrayExpression > Literal + SpreadElement`
-* adjacent sibling: `VariableDeclaration ~ VariableDeclaration`
+* following sibling: `VariableDeclaration ~ VariableDeclaration`
+* adjacent sibling: `ArrayExpression > Literal + SpreadElement`
 * negation: `:not(ForStatement)`
 * matches-any: `:matches([attr] > :first-child, :last-child)`
 * class of AST node: `:statement`, `:expression`, `:declaration`, `:function`, or `:pattern`
index 2181d1b44f874fe9903fa3f3e4424e8f803f6721..7b57f3fbf284f0f8d2ea1ce6f0e923727e1f1aec 100644 (file)
@@ -93,7 +93,7 @@ You can also omit the `eslint-config` and it will be automatically assumed by ES
 }
 ```
 
-The module name can also be customized, just note that when using [scoped modules](https://docs.npmjs.com/misc/scope) it is not possible to omit the `eslint-config-` prefix. Doing so would result in package naming conflicts, and thus in resolution errors in most of cases. For example a package named `@scope/eslint-config-myconfig` vs `@scope/my-config`, since both are valid scoped package names, the configuration should be specified as:
+The module name can also be customized, just note that when using [scoped modules](https://docs.npmjs.com/misc/scope) it is not possible to omit the `eslint-config-` prefix. Doing so would result in package naming conflicts, and thus in resolution errors in most of cases. For example a package named `@scope/eslint-config-myconfig` vs `@scope/myconfig`, since both are valid scoped package names, the configuration should be specified as:
 
 ```json
 {
index 521b9eb5aacf1d712a583c7908d1d12aadf12412..3809e3700d079fe3c01e26bc9b2f79eabe587142 100644 (file)
@@ -136,7 +136,7 @@ overrides:
     processor: a-plugin/markdown
 ```
 
-See [Specifying Processor](../user-guide/configuring.md#specifying-processor) for details.
+See [Specifying Processor](../user-guide/configuring/plugins.md#specifying-processor) for details.
 
 #### File Extension-named Processor
 
@@ -197,7 +197,7 @@ If the example plugin above were called `eslint-plugin-myPlugin`, the `myConfig`
 
 ```
 
-**Note:** Please note that configuration will not enable any of the plugin's rules by default, and instead should be treated as a standalone config. This means that you must specify your plugin name in the `plugins` array as well as any rules you want to enable that are part of the plugin. Any plugin rules must be prefixed with the short or long plugin name. See [Configuring Plugins](../user-guide/configuring.md#configuring-plugins) for more information.
+**Note:** Please note that configuration will not enable any of the plugin's rules by default, and instead should be treated as a standalone config. This means that you must specify your plugin name in the `plugins` array as well as any rules you want to enable that are part of the plugin. Any plugin rules must be prefixed with the short or long plugin name. See [Configuring Plugins](../user-guide/configuring/plugins.md#configuring-plugins) for more information.
 
 ### Peer Dependency
 
index a80f3316405bc33ce1448ef8a2fbcdf7fe788593..5265cb50e79297f97b9ade9eedafc319e83e78c0 100644 (file)
@@ -1,4 +1,6 @@
-# Working with Rules
+# Working with Rules (Deprecated)
+
+**Note:** This page covers the deprecated rule format for ESLint <= 2.13.1. [This is the most recent rule format](./working-with-rules.md).
 
 Each rule in ESLint has two files named with its identifier (for example, `no-extra-semi`).
 
@@ -32,7 +34,7 @@ module.exports.schema = []; // no options
 
 ## Rule Basics
 
-`schema` (array) specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../user-guide/configuring.md#configuring-rules)
+`schema` (array) specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../user-guide/configuring/rules.md#configuring-rules)
 
 `create` (function) returns an object with methods that ESLint calls to "visit" nodes while traversing the abstract syntax tree (AST as defined by [ESTree](https://github.com/estree/estree)) of JavaScript code:
 
@@ -72,7 +74,7 @@ module.exports = function(context) {
 
 The `context` object contains additional functionality that is helpful for rules to do their jobs. As the name implies, the `context` object contains information that is relevant to the context of the rule. The `context` object has the following properties:
 
-* `parserOptions` - the parser options configured for this run (more details [here](../user-guide/configuring.md#specifying-parser-options)).
+* `parserOptions` - the parser options configured for this run (more details [here](../user-guide/configuring/language-options.md#specifying-parser-options)).
 * `id` - the rule ID.
 * `options` - an array of rule options.
 * `settings` - the `settings` from configuration.
@@ -476,7 +478,7 @@ valid: [
 ]
 ```
 
-The options available and the expected syntax for `parserOptions` is the same as those used in [configuration](../user-guide/configuring.md#specifying-parser-options).
+The options available and the expected syntax for `parserOptions` is the same as those used in [configuration](../user-guide/configuring/language-options.md#specifying-parser-options).
 
 ### Write Several Tests
 
@@ -571,5 +573,5 @@ The thing that makes ESLint different from other linters is the ability to defin
 Runtime rules are written in the same format as all other rules. Create your rule as you would any other and then follow these steps:
 
 1. Place all of your runtime rules in the same directory (i.e., `eslint_rules`).
-2. Create a [configuration file](../user-guide/configuring.md) and specify your rule ID error level under the `rules` key. Your rule will not run unless it has a value of `1` or `2` in the configuration file.
+2. Create a [configuration file](../user-guide/configuring/) and specify your rule ID error level under the `rules` key. Your rule will not run unless it has a value of `1` or `2` in the configuration file.
 3. Run the [command line interface](../user-guide/command-line-interface.md) using the `--rulesdir` option to specify the location of your runtime rules.
index 62e91ebae9148a7e1156e236d319a678d5d560c5..9dadae00e5097439e08660ab06c1e1142db094f6 100644 (file)
@@ -60,7 +60,7 @@ The source file for a rule exports an object with the following properties.
 
     * `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.md#extending-configuration-files) enables the rule
+    * `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)
 
@@ -70,7 +70,7 @@ The source file for a rule exports an object with the following properties.
 
     **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.
 
-* `schema` (array) specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../user-guide/configuring.md#configuring-rules)
+* `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.
 
@@ -117,10 +117,10 @@ module.exports = {
 
 The `context` object contains additional functionality that is helpful for rules to do their jobs. As the name implies, the `context` object contains information that is relevant to the context of the rule. The `context` object has the following properties:
 
-* `parserOptions` - the parser options configured for this run (more details [here](../user-guide/configuring.md#specifying-parser-options)).
+* `parserOptions` - the parser options configured for this run (more details [here](../user-guide/configuring/language-options.md#specifying-parser-options)).
 * `id` - the rule ID.
-* `options` - an array of the [configured options](/docs/user-guide/configuring.md#configuring-rules) for this rule. This array does not include the rule severity. For more information, see [here](#contextoptions).
-* `settings` - the [shared settings](/docs/user-guide/configuring.md#adding-shared-settings) from configuration.
+* `options` - an array of the [configured options](/docs/user-guide/configuring/rules.md#configuring-rules) for this rule. This array does not include the rule severity. For more information, see [here](#contextoptions).
+* `settings` - the [shared settings](/docs/user-guide/configuring/configuration-files.md#adding-shared-settings) from configuration.
 * `parserPath` - the name of the `parser` from configuration.
 * `parserServices` - an object containing parser-provided services for rules. The default parser does not provide any services. However, if a rule is intended to be used with a custom parser, it could use `parserServices` to access anything provided by that parser. (For example, a TypeScript parser could provide the ability to get the computed type of a given node.)
 
@@ -139,8 +139,9 @@ Additionally, the `context` object has the following methods:
     * If the node is an `ImportSpecifier`, `ImportDefaultSpecifier`, or `ImportNamespaceSpecifier`, the declared variable is returned.
     * Otherwise, if the node does not declare any variables, an empty array is returned.
 * `getFilename()` - returns the filename associated with the source.
+* `getPhysicalFilename()` - when linting a file, it returns the full path of the file on disk without any code block information. When linting text, it returns the value passed to `—stdin-filename` or `<text>` if not specified.
 * `getScope()` - returns the [scope](./scope-manager-interface.md#scope-interface) of the currently-traversed node. This information can be used to track references to variables.
-* `getSourceCode()` - returns a [`SourceCode`](#context-getsourcecode) object that you can use to work with the source that was passed to ESLint.
+* `getSourceCode()` - returns a [`SourceCode`](#contextgetsourcecode) object that you can use to work with the source that was passed to ESLint.
 * `markVariableAsUsed(name)` - marks a variable with the given name in the current scope as used. This affects the [no-unused-vars](../rules/no-unused-vars.md) rule. Returns `true` if a variable with the given name was found and marked as used, otherwise `false`.
 * `report(descriptor)` - reports a problem in the code (see the [dedicated section](#contextreport)).
 
@@ -375,7 +376,7 @@ context.report({
 {% endraw %}
 ```
 
-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 confirm to user preferences on presence/absence of semicolons. All of those things can be corrected by multipass autofix when the user triggers it.
+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:
 
@@ -737,5 +738,5 @@ The thing that makes ESLint different from other linters is the ability to defin
 Runtime rules are written in the same format as all other rules. Create your rule as you would any other and then follow these steps:
 
 1. Place all of your runtime rules in the same directory (e.g., `eslint_rules`).
-2. Create a [configuration file](../user-guide/configuring.md) and specify your rule ID error level under the `rules` key. Your rule will not run unless it has a value of `"warn"` or `"error"` in the configuration file.
+2. Create a [configuration file](../user-guide/configuring/) and specify your rule ID error level under the `rules` key. Your rule will not run unless it has a value of `"warn"` or `"error"` in the configuration file.
 3. Run the [command line interface](../user-guide/command-line-interface.md) using the `--rulesdir` option to specify the location of your runtime rules.
index ad610253d151f73a41ef107273c741c3629dd6cc..2624a4bda0811c931aa26348b389a3fb2a857600 100644 (file)
@@ -20,39 +20,67 @@ There are four primary issue categories:
 
 The first goal when evaluating an issue is to determine which category the issue falls into.
 
-## When an Issue is Opened
+## Triaging Process
 
-When an issue is opened, the bot will automatically apply the "triage" label. Issues labeled with "triage" are the ones that need to be looked at by team members to determine what to do next.
+All of ESLint's issues, across all GitHub repositories, are managed on our [Triage Project](https://github.com/orgs/eslint/projects/2). Please use the Triage project instead of the issues list when reviewing issues to determine what to work on. The Triage project has several columns:
+
+* **Needs Triage** - issues that have not yet been reviewed by anyone
+* **Triaging** - issues that someone has reviewed but has not been able to fully triage yet
+* **Ready for Dev Team** - issues that have been triaged and have all of the information necessary for the dev team to take a look
+* **Evaluating** - the dev team is evaluating these issues to determine whether to move forward or not
+* **Feedback Needed** - a team member is requesting more input from the rest of the team before proceeding
+* **Waiting for RFC** - the next step in the process is for an RFC to be written
+* **RFC Opened** - an RFC is opened to address these issues
+* **Blocked** - the issue can't move forward due to some dependency
+* **Ready to Implement** - these issues have all of the details necessary to start implementation
+* **PR Opened** - there is an open pull request for each of these issues
+* **Completed** - the issue has been closed (either via pull request merge or by the team manually closing the issue)
+
+We make every attempt to automate movement between as many columns as we can, but sometimes moving issues needs to be done manually.
+
+### When an Issue is Opened
+
+When an issue is opened, it is automatically added to the "Needs Triage" column in the Triage project. These issues need to be evaluated to determine next steps. Anyone on the support team or dev team can follow these steps to properly triage issues.
+
+**Note:** If an issue is in the "Triaging" column, that means someone is already triaging it and you should let them finish. There's no need to comment on issues in the "Triaging" column unless someone asks for help.
 
 The steps for triaging an issue are:
 
-1. Is it clear what is being requested?
-    * No: add the "needs info" label to the issue. The bot will add a comment asking for more information. You don't need to comment any further until the person who opened the issue responds with the information requested from the bot.
-    * Yes:
-        * Remove the "triage" label
-        * Label questions with the "question" label
-        * Label bug reports with the "bug" label (also use the "accepted" label if you can reproduce and verify the bug, otherwise add the "evaluating" label to indicate someone needs to verify)
-        * Label requests for changes to existing features (new rule options, new configuration options, etc.) with the "enhancement" and "evaluating" labels
-        * Label requests for completely new features (new rules, supporting a new file format, etc.) with the "feature" and "evaluating" labels
-        * Use an appropriate label for the part of the project the issue refers to:
+1. Move the issue from "Needs Triage" to "Triaging" in the Triage project
+1. Check: Has all of the information in the issue template been provided?
+    * **No:** If information is missing from the issue template, or you can't tell what is being requested, please ask the author to provide the missing information:
+        * Add the "needs info" label to the issue so we know that this issue is stalled due to lack of information.
+        * Don't move on to other steps until the necessary information has been provided.
+        * If the issue author hasn't provided the necessary information after 7 days, please close the issue. The bot will add a comment stating that the issue was closed because there was information missing.
+    * **Yes:**
+        * If the issue is actually a question (rather than something the dev team needs to change), please [convert it to a discussion](https://docs.github.com/en/free-pro-team@latest/discussions/managing-discussions-for-your-community/moderating-discussions#converting-an-issue-to-a-discussion). You can continue the conversation as a discussion.
+        * If the issue is reporting a bug, try to reproduce the issue following the instructions in the issue. If you can reproduce the bug, please add the "repro:yes" label. (The bot will automatically remove the "repro:needed" label.) If you can't reproduce the bug, ask the author for more information about their environment or to clarify reproduction steps.
+        * If the issue is reporting something that works as intended, please add the "works as intended" label and close the issue.
+        * For all issues, please add labels describing the part of ESLint affected:
+            * "3rd party plugin" - related to third-party functionality (plugins, parsers, rules, etc.)
             * "build" - related to commands run during a build (testing, linting, release scripts, etc.)
             * "cli" - related to command line input or output, or to `CLIEngine`
             * "core" - related to internal APIs
             * "documentation" - related to content on eslint.org
             * "infrastructure" - related to resources needed for builds or deployment (VMs, CI tools, bots, etc.)
-1. Once it's clear what type of issue it is, make sure all of the relevant information is provided:
-    * **Bugs**: See [bug reporting guidelines](/docs/developer-guide/contributing/reporting-bugs.md)
-    * **New Rules:** See [rule proposal guidelines](/docs/developer-guide/contributing/new-rules.md)
-    * **Rule Changes:** See [rule change proposal guidelines](/docs/developer-guide/contributing/rule-changes.md)
-    * **Other Changes:** See [change proposal guidelines](/docs/developer-guide/contributing/changes.md)
+            * "rule" - related to core rules
+        * If you can't properly triage the issue, move the issue back to the "Needs Triage" column in the Triage project so someone else can triage it
+        * If you have triaged the issue, move the issue to the "Ready for Dev Team" column in the Triage project
+
+## Evaluation Process
+
+When an issue has been moved to the "Ready for Dev Team" column, any dev team member can pick up the issue to start evaluating it.
+
+1. Move the issue into the "Evaluating" column.
 1. Next steps:
-    * **Questions:** answer the question and close the issue when the conversation is over.
     * **Bugs:** if you can verify the bug, add the "accepted" label and ask if they would like to submit a pull request.
     * **New Rules:** if you are willing to champion the rule (meaning you believe it should be included in ESLint core and you will take ownership of the process for including it), add a comment saying you will champion the issue, assign the issue to yourself, and follow the [guidelines](#championing-issues) below.
     * **Rule Changes:** if you are willing to champion the change and it would not be a breaking change (requiring a major version increment), add a comment saying that you will champion the issue, assign the issue to yourself, and follow the [guidelines](#championing-issues) below.
     * **Breaking Changes:** if you suspect or can verify that a change would be breaking, label it as "Breaking".
     * **Duplicates:** if you can verify the issue is a duplicate, add a comment mentioning the duplicate issue (such as, "Duplicate of #1234") and close the issue.
 1. Regardless of the above, always leave a comment. Don't just add labels, engage with the person who opened the issue by asking a question (request more information if necessary) or stating your opinion of the issue. If it's a verified bug, ask if the user would like to submit a pull request.
+1. If the issue can't be implemented because it needs an external dependency to be updated or needs to wait for another issue to be resolved, move the issue to the "Blocked" column.
+1. If the issue has been accepted and an RFC is required as the next step, move the issue to the "Waiting for RFC" column and comment on the issue that an RFC is needed.
 
 **Note:** "Good first issue" issues are intended to help new contributors feel welcome and empowered to make a contribution to ESLint. To ensure that new contributors are given a chance to work on these issues, issues labeled "good first issue" must be open for 30 days *from the day the issue was labeled* before a team member is permitted to work on them.
 
@@ -65,6 +93,8 @@ Issues may be labeled as "accepted" when the issue is:
 
 The "accepted" label will be added to other issues by a TSC member if it's appropriate for the roadmap.
 
+When an issue is accepted and implementation can begin, it should be moved to the "Ready to Implement" column.
+
 ## Championing Issues
 
 New rules and rule changes require a champion. As champion, it's your job to:
@@ -118,4 +148,3 @@ In an effort to keep the issues backlog manageable, team members may also close
 * **Unaccepted**: Close after it has been open for 21 days, as these issues do not have enough support to move forward.
 * **Accepted**: Close after 90 days if no one from the team or the community is willing to step forward and own the work to complete to it.
 * **Help wanted:** Close after 90 days if it has not been completed.
-
index e4fc5845af2412c0efb9d93bfa3bf235b96fa6a3..144ebdf071035db463a2274d471a3ba433ab2d11 100644 (file)
@@ -52,7 +52,7 @@ This rule has a string option and an object one.
 String options are:
 
 * `"always"` (default) requires parens around arguments in all cases.
-* `"as-needed"` enforces no braces where they can be omitted.
+* `"as-needed"` enforces no parens where they can be omitted.
 
 Object properties for variants of the `"as-needed"` option:
 
index 97be55b889cf4b7033c3c533d4eeb2497329a626..75c7db025807c6d00d7140d98d2504cb592c2a17 100644 (file)
@@ -34,6 +34,13 @@ function doTryCatch() {
         var f = build;
     }
 }
+
+function doFor() {
+    for (var x = 1; x < 10; x++) {
+        var y = f(x);
+    }
+    console.log(y);
+}
 ```
 
 Examples of **correct** code for this rule:
@@ -71,6 +78,13 @@ function doTryCatch() {
         f = build;
     }
 }
+
+function doFor() {
+    for (var x = 1; x < 10; x++) {
+        var y = f(x);
+        console.log(y);
+    }
+}
 ```
 
 ## Further Reading
index 7c2e6b7a623869951c97b56f88989ee91588a765..2a626604e01c264168cb4359a0d487bc26ec5c0f 100644 (file)
@@ -68,6 +68,7 @@ if (a) {
 
 This rule has an object option:
 
+* `"ignoredNodes"` can be used to disable indentation checking for any AST node. This accepts an array of [selectors](/docs/developer-guide/selectors.md). If an AST node is matched by any of the selectors, the indentation of tokens which are direct children of that node will be ignored. This can be used as an escape hatch to relax the rule if you disagree with the indentation that it enforces for a particular syntactic pattern.
 * `"SwitchCase"` (default: 0) enforces indentation level for `case` clauses in `switch` statements
 * `"VariableDeclarator"` (default: 1) enforces indentation level for `var` declarators; can also take an object to define separate rules for `var`, `let` and `const` declarations. It can also be `"first"`, indicating all the declarators should be aligned with the first declarator.
 * `"outerIIFEBody"` (default: 1) enforces indentation level for file-level IIFEs. This can also be set to `"off"` to disable checking for file-level IIFEs.
@@ -85,7 +86,6 @@ This rule has an object option:
 * `"ImportDeclaration"` (default: 1) enforces indentation level for import statements. It can be set to the string `"first"`, indicating that all imported members from a module should be aligned with the first member in the list. This can also be set to `"off"` to disable checking for imported module members.
 * `"flatTernaryExpressions": true` (`false` by default) requires no indentation for ternary expressions which are nested in other ternary expressions.
 * `"offsetTernaryExpressions": true` (`false` by default) requires indentation for values of ternary expressions.
-* `"ignoredNodes"` accepts an array of [selectors](/docs/developer-guide/selectors.md). If an AST node is matched by any of the selectors, the indentation of tokens which are direct children of that node will be ignored. This can be used as an escape hatch to relax the rule if you disagree with the indentation that it enforces for a particular syntactic pattern.
 * `"ignoreComments"` (default: false) can be used when comments do not need to be aligned with nodes on the previous or next line.
 
 Level of indentation denotes the multiple of the indent specified. Example:
@@ -133,6 +133,41 @@ if (a) {
 }
 ```
 
+### ignoredNodes
+
+The following configuration ignores the indentation of `ConditionalExpression` ("ternary expression") nodes:
+
+Examples of **correct** code for this rule with the `4, { "ignoredNodes": ["ConditionalExpression"] }` option:
+
+```js
+/*eslint indent: ["error", 4, { "ignoredNodes": ["ConditionalExpression"] }]*/
+
+var a = foo
+      ? bar
+      : baz;
+
+var a = foo
+                ? bar
+: baz;
+```
+
+The following configuration ignores indentation in the body of IIFEs.
+
+Examples of **correct** code for this rule with the `4, { "ignoredNodes": ["CallExpression > FunctionExpression.callee > BlockStatement.body"] }` option:
+
+```js
+/*eslint indent: ["error", 4, { "ignoredNodes": ["CallExpression > FunctionExpression.callee > BlockStatement.body"] }]*/
+
+(function() {
+
+foo();
+bar();
+
+})
+```
+
+All AST node types can be found at [ESTree](https://github.com/estree/estree) specification. You can use [AST Explorer](https://astexplorer.net/) with the espree parser to examine AST tree of a code snippet.
+
 ### SwitchCase
 
 Examples of **incorrect** code for this rule with the `2, { "SwitchCase": 1 }` options:
@@ -740,39 +775,6 @@ condition
       }
 ```
 
-### ignoredNodes
-
-The following configuration ignores the indentation of `ConditionalExpression` ("ternary expression") nodes:
-
-Examples of **correct** code for this rule with the `4, { "ignoredNodes": ["ConditionalExpression"] }` option:
-
-```js
-/*eslint indent: ["error", 4, { "ignoredNodes": ["ConditionalExpression"] }]*/
-
-var a = foo
-      ? bar
-      : baz;
-
-var a = foo
-                ? bar
-: baz;
-```
-
-The following configuration ignores indentation in the body of IIFEs.
-
-Examples of **correct** code for this rule with the `4, { "ignoredNodes": ["CallExpression > FunctionExpression.callee > BlockStatement.body"] }` option:
-
-```js
-/*eslint indent: ["error", 4, { "ignoredNodes": ["CallExpression > FunctionExpression.callee > BlockStatement.body"] }]*/
-
-(function() {
-
-foo();
-bar();
-
-})
-```
-
 ### ignoreComments
 
 Examples of additional **correct** code for this rule with the `4, { "ignoreComments": true }` option:
index de4ea0d94f2898fcee54f82eb2ae74d65eb5e19c..74b9aada2287663157c2f6d1c5fbdbcb1890656a 100644 (file)
@@ -18,7 +18,7 @@ This rule has a number or object option:
 
 * `"skipComments": true` ignore lines containing just comments
 
-### code
+### max
 
 Examples of **incorrect** code for this rule with a max value of `2`:
 
index 683f31584a1fe2893a45cb80231db64feb215c36..437d2c3daa10118402ff122033835c05d561361a 100644 (file)
@@ -12,7 +12,7 @@ import { find } from 'module';
 
 ## Rule Details
 
-This rule requires that all imports from a single module exists in a single `import` statement.
+This rule requires that all imports from a single module that can be merged exist in a single `import` statement.
 
 Example of **incorrect** code for this rule:
 
@@ -33,6 +33,16 @@ import { merge, find } from 'module';
 import something from 'another-module';
 ```
 
+Example of **correct** code for this rule:
+
+```js
+/*eslint no-duplicate-imports: "error"*/
+
+// not mergeable
+import { merge } from 'module';
+import * as something from 'module';
+```
+
 ## Options
 
 This rule takes one optional argument, an object with a single key, `includeExports` which is a `boolean`. It defaults to `false`.
@@ -58,3 +68,17 @@ import { merge, find } from 'module';
 
 export { find };
 ```
+
+Example of **correct** code for this rule with the `{ "includeExports": true }` option:
+
+```js
+/*eslint no-duplicate-imports: ["error", { "includeExports": true }]*/
+
+import { merge, find } from 'module';
+
+// cannot be merged with the above import
+export * as something from 'module';
+
+// cannot be written differently
+export * from 'module';
+```
index f78e40c66c2a6a311625f3fd8c2c33942fa7952f..8ccee339f0547378494c26012601d8e1033e0387 100644 (file)
@@ -33,10 +33,11 @@ This rule is aimed to flag shorter notations for the type conversion, then sugge
 
 This rule has three main options and one override option to allow some coercions as required.
 
-* `"boolean"` (`true` by default) - When this is `true`, this rule warns shorter type conversions for `boolean` type.
-* `"number"` (`true` by default) - When this is `true`, this rule warns shorter type conversions for `number` type.
-* `"string"` (`true` by default) - When this is `true`, this rule warns shorter type conversions for `string` type.
-* `"allow"` (`empty` by default) - Each entry in this array can be one of `~`, `!!`, `+` or `*` that are to be allowed.
+-   `"boolean"` (`true` by default) - When this is `true`, this rule warns shorter type conversions for `boolean` type.
+-   `"number"` (`true` by default) - When this is `true`, this rule warns shorter type conversions for `number` type.
+-   `"string"` (`true` by default) - When this is `true`, this rule warns shorter type conversions for `string` type.
+-   `"disallowTemplateShorthand"` (`false` by default) - When this is `true`, this rule warns `string` type conversions using `${expression}` form.
+-   `"allow"` (`empty` by default) - Each entry in this array can be one of `~`, `!!`, `+` or `*` that are to be allowed.
 
 Note that operator `+` in `allow` list would allow `+foo` (number coercion) as well as `"" + foo` (string coercion).
 
@@ -106,6 +107,42 @@ var s = String(foo);
 foo = String(foo);
 ```
 
+### disallowTemplateShorthand
+
+This option is **not** affected by the `string` option.
+
+Examples of **incorrect** code for the `{ "disallowTemplateShorthand": true }` option:
+
+```js
+/*eslint no-implicit-coercion: ["error", { "disallowTemplateShorthand": true }]*/
+
+var s = `${foo}`;
+```
+
+Examples of **correct** code for the `{ "disallowTemplateShorthand": true }` option:
+
+```js
+/*eslint no-implicit-coercion: ["error", { "disallowTemplateShorthand": true }]*/
+
+var s = String(foo);
+
+var s = `a${foo}`;
+
+var s = `${foo}b`;
+
+var s = `${foo}${bar}`;
+
+var s = tag`${foo}`;
+```
+
+Examples of **correct** code for the default `{ "disallowTemplateShorthand": false }` option:
+
+```js
+/*eslint no-implicit-coercion: ["error", { "disallowTemplateShorthand": false }]*/
+
+var s = `${foo}`;
+```
+
 ### allow
 
 Using `allow` list, we can override and allow specific operators.
index 674a847f7fb11a06416377ddf5f86fef4f55ec85..33ce7f658313bc5fc2b1940f13fac8222ae488d4 100644 (file)
@@ -122,6 +122,10 @@ var foo = a & b | c;
 /*eslint no-mixed-operators: ["error", {"groups": [["&&", "||", "?:"]]}]*/
 
 var foo = a || b ? c : d;
+
+var bar = a ? b || c : d;
+
+var baz = a ? b : c || d;
 ```
 
 Examples of **correct** code for this rule with `{"groups": [["&", "|", "^", "~", "<<", ">>", ">>>"], ["&&", "||"]]}` option:
@@ -145,6 +149,11 @@ var foo = (a + b) * c;
 
 var foo = (a || b) ? c : d;
 var foo = a || (b ? c : d);
+
+var bar = a ? (b || c) : d;
+
+var baz = a ? b : (c || d);
+var baz = (a ? b : c) || d;
 ```
 
 ### allowSamePrecedence
index 41bba9ad6a91db378103951c2a2134bfde6da55d..7ef70567593763696ed2daa4640f4b28f6a2f769 100644 (file)
@@ -43,6 +43,38 @@ let a = c;
 let b = c;
 ```
 
+## 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
+
+Examples of **correct** code for the `{ "ignoreNonDeclaration": true }` option:
+
+```js
+/*eslint no-multi-assign: ["error", { "ignoreNonDeclaration": true }]*/
+
+let a;
+let b;
+a = b = "baz";
+
+const x = {};
+const y = {};
+x.one = y.one = 1;
+```
+
+Examples of **incorrect** code for the `{ "ignoreNonDeclaration": true }` option:
+
+```js
+/*eslint no-multi-assign: ["error", { "ignoreNonDeclaration": true }]*/
+
+let a = b = "baz";
+
+const foo = bar = 1;
+```
+
 ## Related Rules
 
 * [max-statements-per-line](max-statements-per-line.md)
index 0f6841340c964df4125dd005d23fd0e212ab9822..f1577d7d89d6ffd79a4923eac01bffd39f6f7989 100644 (file)
@@ -75,7 +75,21 @@ or like this if you need to restrict only certain imports from a module:
 }]
 ```
 
-The custom message will be appended to the default error message. Please note that you may not specify custom error messages for restricted patterns as a particular import may match more than one pattern.
+or like this if you want to apply a custom message to pattern matches:
+
+```json
+"no-restricted-imports": ["error", {
+    "patterns": [{
+      "group": ["import1/private/*"],
+      "message": "usage of import1 private modules not allowed."
+    }, {
+      "group": ["import2/*", "!import2/good"],
+      "message": "import2 is deprecated, except the modules in import2/good."
+    }]
+}]
+```
+
+The custom message will be appended to the default error message.
 
 To restrict the use of all Node.js core imports (via https://github.com/nodejs/node/tree/master/lib):
 
@@ -149,6 +163,15 @@ import { DisallowedObject as AllowedObject } from "foo";
 import * as Foo from "foo";
 ```
 
+```js
+/*eslint no-restricted-imports: ["error", { patterns: [{
+    group: ["lodash/*"],
+    message: "Please use the default import from 'lodash' instead."
+}]}]*/
+
+import pick from 'lodash/pick';
+```
+
 Examples of **correct** code for this rule:
 
 ```js
@@ -182,6 +205,15 @@ import DisallowedObject from "foo"
 import { AllowedObject as DisallowedObject } from "foo";
 ```
 
+```js
+/*eslint no-restricted-imports: ["error", { patterns: [{
+    group: ["lodash/*"],
+    message: "Please use the default import from 'lodash' instead."
+}]}]*/
+
+import lodash from 'lodash';
+```
+
 ## When Not To Use It
 
 Don't use this rule or don't include a module in the list for this rule if you want to be able to import a module in your project without an ESLint error or warning.
index 602a412f16ad65d9c5cfd9b9986e72d2f6202deb..f12c46573b7c9b8294f03d9e63b2aa115c5423d7 100644 (file)
@@ -17,7 +17,7 @@ while (a = next(), a && a.length);
 This rule forbids the use of the comma operator, with the following exceptions:
 
 * In the initialization or update portions of a `for` statement.
-* If the expression sequence is explicitly wrapped in parentheses.
+* By default, if the expression sequence is explicitly wrapped in parentheses. This exception can be removed with the `allowInParentheses` option.
 
 Examples of **incorrect** code for this rule:
 
@@ -63,10 +63,76 @@ while ((val = foo(), val < 42));
 with ((doSomething(), val)) {}
 ```
 
+### Note about arrow function bodies
+
+If an arrow function body is a statement rather than a block, and that statement contains a sequence, you need to use double parentheses around the statement to indicate that the sequence is intentional.
+
+Examples of **incorrect** code for arrow functions:
+
+```js
+/*eslint no-sequences: "error"*/
+const foo = (val) => (console.log('bar'), val);
+
+const foo = () => ((bar = 123), 10);
+
+const foo = () => { return (bar = 123), 10 }
+```
+
+Examples of **correct** code for arrow functions:
+
+```js
+/*eslint no-sequences: "error"*/
+const foo = (val) => ((console.log('bar'), val));
+
+const foo = () => (((bar = 123), 10));
+
+const foo = () => { return ((bar = 123), 10) }
+```
+
+## Options
+
+This rule takes one option, an object, with the following properties:
+
+* `"allowInParentheses"`: If set to `true` (default), this rule allows expression sequences that are explicitly wrapped in parentheses.
+
+### allowInParentheses
+
+Examples of **incorrect** code for this rule with the `{ "allowInParentheses": false }` option:
+
+```js
+/*eslint no-sequences: ["error", { "allowInParentheses": false }]*/
+
+foo = (doSomething(), val);
+
+(0, eval)("doSomething();");
+
+do {} while ((doSomething(), !!test));
+
+for (; (doSomething(), !!test); );
+
+if ((doSomething(), !!test));
+
+switch ((val = foo(), val)) {}
+
+while ((val = foo(), val < 42));
+
+with ((doSomething(), val)) {}
+
+const foo = (val) => ((console.log('bar'), val));
+```
+
+Examples of **correct** code for this rule with the `{ "allowInParentheses": false }` option:
+
+```js
+/*eslint no-sequences: ["error", { "allowInParentheses": false }]*/
+
+for (i = 0, j = 10; i < j; i++, j--);
+```
+
 ## When Not To Use It
 
 Disable this rule if sequence expressions with the comma operator are acceptable.
-Another case is where you might want to report all usages of the comma operator, even if they are wrapped in parentheses or in a for loop. You can achieve this using rule `no-restricted-syntax`:
+Another case is where you might want to report all usages of the comma operator, even in a for loop. You can achieve this using rule `no-restricted-syntax`:
 
 ```js
 {
index faaf978cc8dc5a9c6cfd4ad542a36a6eaa2f53ab..a7de8052357dba5610349fa7bd7faf97c468a50e 100644 (file)
@@ -68,7 +68,7 @@ if(typeof a === "string"){}
 
 ## Environments
 
-For convenience, ESLint provides shortcuts that pre-define global variables exposed by popular libraries and runtime environments. This rule supports these environments, as listed in [Specifying Environments](../user-guide/configuring.md#specifying-environments).  A few examples are given below.
+For convenience, ESLint provides shortcuts that pre-define global variables exposed by popular libraries and runtime environments. This rule supports these environments, as listed in [Specifying Environments](../user-guide/configuring/language-options.md#specifying-environments).  A few examples are given below.
 
 ### browser
 
index 48296aac2be9c75487113e50dbcf87f21723a4d1..49fa415e0c0e34f60d40bbd6441eb3f278002a02 100644 (file)
@@ -92,6 +92,8 @@ obj?.foo();
 
 obj?.foo.bar;
 
+obj.foo?.bar;
+
 foo?.()?.bar;
 
 (obj?.foo ?? bar)`template`;
index 8a9748d3d06dfbefafbb51338a688a765e39174d..1557725a19301089eab77f8c33f39bde084fdbfe 100644 (file)
@@ -31,6 +31,7 @@ This rule, in its default state, does not require any arguments. If you would li
 * `allowShortCircuit` set to `true` will allow you to use short circuit evaluations in your expressions (Default: `false`).
 * `allowTernary` set to `true` will enable you to use ternary operators in your expressions similarly to short circuit evaluations (Default: `false`).
 * `allowTaggedTemplates` set to `true` will enable you to use tagged template literals in your expressions (Default: `false`).
+* `enforceForJSX` set to `true` will flag unused JSX element expressions (Default: `false`).
 
 These options allow unused expressions *only if all* of the code paths either directly change the state (for example, assignment statement) or could have *side effects* (for example, function call).
 
@@ -161,3 +162,27 @@ Examples of **correct** code for the `{ "allowTaggedTemplates": true }` option:
 
 tag`some tagged template string`;
 ```
+
+### enforceForJSX
+
+JSX is most-commonly used in the React ecosystem, where it is compiled to `React.createElement` expressions. Though free from side-effects, these calls are not automatically flagged by the `no-unused-expression` rule. If you're using React, or any other side-effect-free JSX pragma, this option can be enabled to flag these expressions.
+
+Examples of **incorrect** code for the `{ "enforceForJSX": true }` option:
+
+```jsx
+/*eslint no-unused-expressions: ["error", { "enforceForJSX": true }]*/
+
+<MyComponent />;
+
+<></>;
+```
+
+Examples of **correct** code for the `{ "enforceForJSX": true }` option:
+
+```jsx
+/*eslint no-unused-expressions: ["error", { "enforceForJSX": true }]*/
+
+var myComponentPartial = <MyComponent />;
+
+var myFragment = <></>;
+```
index 2b3f093cc99830b3f28ac87ef93903f4256c14f0..30647031eb1513d4440ad9da1b959cedc05e47e5 100644 (file)
@@ -74,7 +74,7 @@ myFunc = setTimeout(function() {
     myFunc();
 }, 50);
 
-// Only the second argument from the descructured array is used.
+// Only the second argument from the destructured array is used.
 function getY([, y]) {
     return y;
 }
@@ -214,8 +214,11 @@ Examples of **correct** code for the `{ "ignoreRestSiblings": true }` option:
 
 ```js
 /*eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/
-// 'type' is ignored because it has a rest property sibling.
-var { type, ...coords } = data;
+// 'foo' and 'bar' were ignored because they have a rest property sibling.
+var { foo, ...coords } = data;
+
+var bar;
+({ bar, ...coords } = data);
 ```
 
 ### argsIgnorePattern
index 302c81c33b5550465c8c006a6ba23fc81208c81e..cc98b4293c049d852ea6fd7124a42bb83fa67ea5 100644 (file)
@@ -1,17 +1,17 @@
-# enforce consistent line breaks inside braces (object-curly-newline)
+# enforce consistent line breaks after opening and before closing braces (object-curly-newline)
 
 A number of style guides require or disallow line breaks inside of object braces and other tokens.
 
 ## Rule Details
 
-This rule enforces consistent line breaks inside braces of object literals or destructuring assignments.
+This rule requires or disallows a line break between `{` and its following token, and between `}` and its preceding token of object literals or destructuring assignments.
 
 ## Options
 
 This rule has either a string option:
 
-* `"always"` requires line breaks inside braces
-* `"never"` disallows line breaks inside braces
+* `"always"` requires line breaks after opening and before closing braces
+* `"never"` disallows line breaks after opening and before closing braces
 
 Or an object option:
 
@@ -538,7 +538,7 @@ export { foo as f, bar } from 'foo-bar';
 
 ## When Not To Use It
 
-If you don't want to enforce consistent line breaks inside braces, then it's safe to disable this rule.
+If you don't want to enforce consistent line breaks after opening and before closing braces, then it's safe to disable this rule.
 
 ## Related Rules
 
index c9132122ea586423b5f3c4e452f73e5a2b74dafc..7b98aa1f8f66a354d5c890822d0169c8c80e5e66 100644 (file)
@@ -86,7 +86,7 @@ foo(function() { this.a; });
 
 foo(function() { (() => this); });
 
-someArray.map(function(itm) { return this.doSomething(itm); }, someObject);
+someArray.map(function(item) { return this.doSomething(item); }, someObject);
 ```
 
 ## When Not To Use It
index 593ccdd607360b6eaa814bc44fa40f325f205966..8dd313815c805b95c5b3387f9f8d4b13e3589fda 100644 (file)
@@ -84,7 +84,7 @@ async function foo() {
 }
 
 function* bar() {
-  result += yield;
+  result = (yield) + result;
 
   result = (yield somethingElse) + result;
 
index 9df292b6e0de04dde50aa03629af6b7d751d608b..a6d87c20ac1e41c7860483d58667a8bc911d3690 100644 (file)
@@ -42,7 +42,7 @@ In **ECMAScript** modules, which always have strict mode semantics, the directiv
 
 This rule requires or disallows strict mode directives.
 
-This rule disallows strict mode directives, no matter which option is specified, if ESLint configuration specifies either of the following as [parser options](/docs/user-guide/configuring.md#specifying-parser-options):
+This rule disallows strict mode directives, no matter which option is specified, if ESLint configuration specifies either of the following as [parser options](/docs/user-guide/configuring/language-options.md#specifying-parser-options):
 
 * `"sourceType": "module"` that is, files are **ECMAScript** modules
 * `"impliedStrict": true` property in the `ecmaFeatures` object
@@ -66,8 +66,8 @@ This rule has a string option:
 
 The `"safe"` option corresponds to the `"global"` option if ESLint considers a file to be a **Node.js** or **CommonJS** module because the configuration specifies either of the following:
 
-* `node` or `commonjs` [environments](/docs/user-guide/configuring.md#specifying-environments)
-* `"globalReturn": true` property in the `ecmaFeatures` object of [parser options](/docs/user-guide/configuring.md#specifying-parser-options)
+* `node` or `commonjs` [environments](/docs/user-guide/configuring/language-options.md#specifying-environments)
+* `"globalReturn": true` property in the `ecmaFeatures` object of [parser options](/docs/user-guide/configuring/language-options.md#specifying-parser-options)
 
 Otherwise the `"safe"` option corresponds to the `"function"` option. Note that if `"globalReturn": false` is explicitly specified in the configuration, the `"safe"` option will correspond to the `"function"` option regardless of the specified environment.
 
@@ -269,4 +269,4 @@ function foo() {
 
 ## When Not To Use It
 
-In a codebase that has both strict and non-strict code, either turn this rule off, or [selectively disable it](/docs/user-guide/configuring.md) where necessary. For example, functions referencing `arguments.callee` are invalid in strict mode. A [full list of strict mode differences](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode/Transitioning_to_strict_mode#Differences_from_non-strict_to_strict) is available on MDN.
+In a codebase that has both strict and non-strict code, either turn this rule off, or [selectively disable it](/docs/user-guide/configuring/rules.md#disabling-rules) where necessary. For example, functions referencing `arguments.callee` are invalid in strict mode. A [full list of strict mode differences](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode/Transitioning_to_strict_mode#Differences_from_non-strict_to_strict) is available on MDN.
index 8868e99fdb7361311a2dad74b5ff7059e3692452..f8c27e6feba4536766021cc26fb5ac42ed43ada7 100644 (file)
@@ -4,9 +4,6 @@
 
 This rule enforces spacing around the `*` in `yield*` expressions.
 
-To use this rule you either need to [use the `es6` environment](../user-guide/configuring.md#specifying-environments) or
-[set `ecmaVersion` to `6` in `parserOptions`](../user-guide/configuring.md#specifying-parser-options).
-
 ## Options
 
 The rule takes one option, an object, which has two keys `before` and `after` having boolean values `true` or `false`.
index 7339bfe08d1ab63e0680563f8ed9e2b79114edee..1fcb0dfc12e46fe914f682cc9f500ecbdbd3a307 100644 (file)
@@ -10,7 +10,7 @@ Want to skip ahead and just start using ESLint? This section gives a high-level
 
 ESLint has a lot of rules that you can configure to fine-tune it to your project. This section is an exhaustive list of every rule and link to each rule's documentation.
 
-## [Configuring](configuring.md)
+## [Configuring](configuring/)
 
 Once you've got ESLint running, you'll probably want to adjust the configuration to better suit your project. This section explains all the different ways you can configure ESLint.
 
index b8f10497a3640ca25e75461a4c803e39b0e38c0e..99fd61eb950b9d49fe3e7742484a29271fa0b4d9 100644 (file)
@@ -75,6 +75,7 @@ Caching:
   --cache                        Only check changed files - default: false
   --cache-file path::String      Path to the cache file. Deprecated: use --cache-location - default: .eslintcache
   --cache-location path::String  Path to the cache file or directory
+  --cache-strategy String        Strategy to use for detecting changed files - either: metadata or content - default: metadata
 
 Miscellaneous:
   --init                         Run config initialization wizard - default: false
@@ -118,7 +119,7 @@ If `.eslintrc.*` and/or `package.json` files are also used for configuration (i.
 
 #### `--env`
 
-This option enables specific environments. Details about the global variables defined by each environment are available on the [configuration](configuring.md) documentation. This option only enables environments; it does not disable environments set in other configuration files. To specify multiple environments, separate them using commas, or use the option multiple times.
+This option enables specific environments. Details about the global variables defined by each environment are available on the [Specifying Environments](configuring/language-options.md#specifying-environments) documentation. This option only enables environments; it does not disable environments set in other configuration files. To specify multiple environments, separate them using commas, or use the option multiple times.
 
 Examples:
 
@@ -275,7 +276,7 @@ Example:
 
 #### `--ignore-pattern`
 
-This option allows you to specify patterns of files to ignore (in addition to those in `.eslintignore`). You can repeat the option to provide multiple patterns. The supported syntax is the same as for `.eslintignore` [files](./configuring.md#.eslintignore), which use the same patterns as the `.gitignore` [specification](https://git-scm.com/docs/gitignore). You should quote your patterns in order to avoid shell interpretation of glob patterns.
+This option allows you to specify patterns of files to ignore (in addition to those in `.eslintignore`). You can repeat the option to provide multiple patterns. The supported syntax is the same as for `.eslintignore` [files](configuring/ignoring-code.md#the-eslintignore-file), which use the same patterns as the `.gitignore` [specification](https://git-scm.com/docs/gitignore). You should quote your patterns in order to avoid shell interpretation of glob patterns.
 
 Example:
 
@@ -440,6 +441,16 @@ Example:
 
     eslint "src/**/*.js" --cache --cache-location "/Users/user/.eslintcache/"
 
+#### `--cache-strategy`
+
+Strategy for the cache to use for detecting changed files. Can be either `metadata` or `content`. If no strategy is specified, `metadata` will be used.
+
+The `content` strategy can be useful in cases where the modification time of your files change even if their contents have not. For example, this can happen during git operations like git clone because git does not track file modification time.
+
+Example:
+
+    eslint "src/**/*.js" --cache --cache-strategy content
+
 ### Miscellaneous
 
 #### `--init`
@@ -483,7 +494,7 @@ ESLint supports `.eslintignore` files to exclude files from the linting process
     temp.js
     **/vendor/*.js
 
-A more detailed breakdown of supported patterns and directories ESLint ignores by default can be found in [Configuring ESLint](configuring.md#ignoring-files-and-directories).
+A more detailed breakdown of supported patterns and directories ESLint ignores by default can be found in [Ignoring Code](configuring/ignoring-code.md).
 
 ## Exit codes
 
diff --git a/eslint/docs/user-guide/configuring.md b/eslint/docs/user-guide/configuring.md
deleted file mode 100644 (file)
index 96b8d6e..0000000
+++ /dev/null
@@ -1,1210 +0,0 @@
-# Configuring ESLint
-
-ESLint is designed to be completely configurable, meaning you can turn off every rule and run only with basic syntax validation, or mix and match the bundled rules and your custom rules to make ESLint perfect for your project. There are two primary ways to configure ESLint:
-
-1. **Configuration Comments** - use JavaScript comments to embed configuration information directly into a file.
-1. **Configuration Files** - use a JavaScript, JSON or YAML file to specify configuration information for an entire directory and all of its subdirectories. This can be in the form of an [`.eslintrc.*`](#configuration-file-formats) file or an `eslintConfig` field in a [`package.json`](https://docs.npmjs.com/files/package.json) file, both of which ESLint will look for and read automatically, or you can specify a configuration file on the [command line](command-line-interface).
-
-There are several pieces of information that can be configured:
-
-* **Environments** - which environments your script is designed to run in. Each environment brings with it a certain set of predefined global variables.
-* **Globals** - the additional global variables your script accesses during execution.
-* **Rules** - which rules are enabled and at what error level.
-
-All of these options give you fine-grained control over how ESLint treats your code.
-
-## Table of Contents
-
-* [Specifying Parser Options](#specifying-parser-options)
-* [Specifying Parser](#specifying-parser)
-* [Specifying Processor](#specifying-processor)
-* [Specifying Environments](#specifying-environments)
-* [Specifying Globals](#specifying-globals)
-* [Configuring Plugins](#configuring-plugins)
-* [Configuring Rules](#configuring-rules)
-* [Disabling Rules with Inline Comments](#disabling-rules-with-inline-comments)
-* [Configuring Inline Comment Behaviors](#configuring-inline-comment-behaviors)
-* [Adding Shared Settings](#adding-shared-settings)
-* [Using Configuration Files](#using-configuration-files-1)
-* [Configuration File Formats](#configuration-file-formats)
-* [Configuration Cascading and Hierarchy](#configuration-cascading-and-hierarchy)
-* [Extending Configuration Files](#extending-configuration-files)
-* [Configuration Based on Glob Patterns](#configuration-based-on-glob-patterns)
-* [Comments in Configuration Files](#comments-in-configuration-files)
-* [Ignoring Files and Directories](#ignoring-files-and-directories)
-* [Personal Configuration File (deprecated)](#personal-configuration-file-deprecated)
-
-## Specifying Parser Options
-
-ESLint allows you to specify the JavaScript language options you want to support. By default, ESLint expects ECMAScript 5 syntax. You can override that setting to enable support for other ECMAScript versions as well as JSX by using parser options.
-
-Please note that supporting JSX syntax is not the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://github.com/yannickcr/eslint-plugin-react) if you are using React and want React semantics.
-By the same token, supporting ES6 syntax is not the same as supporting new ES6 globals (e.g., new types such as
-`Set`).
-For ES6 syntax, use `{ "parserOptions": { "ecmaVersion": 6 } }`; for new ES6 global variables, use `{ "env":
-{ "es6": true } }`. `{ "env": { "es6": true } }` enables ES6 syntax automatically, but `{ "parserOptions": { "ecmaVersion": 6 } }` does not enable ES6 globals automatically.
-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.
-* `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
-    * `impliedStrict` - enable global [strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode) (if `ecmaVersion` is 5 or greater)
-    * `jsx` - enable [JSX](https://facebook.github.io/jsx/)
-
-Here's an example `.eslintrc.json` file:
-
-```json
-{
-    "parserOptions": {
-        "ecmaVersion": 6,
-        "sourceType": "module",
-        "ecmaFeatures": {
-            "jsx": true
-        }
-    },
-    "rules": {
-        "semi": "error"
-    }
-}
-```
-
-Setting parser options helps ESLint determine what is a parsing error. All language options are `false` by default.
-
-## Specifying Parser
-
-By default, ESLint uses [Espree](https://github.com/eslint/espree) as its parser. You can optionally specify that a different parser should be used in your configuration file so long as the parser meets the following requirements:
-
-1. It must be a Node module loadable from the config file where it appears. Usually, this means you should install the parser package separately using npm.
-1. It must conform to the [parser interface](/docs/developer-guide/working-with-custom-parsers.md).
-
-Note that even with these compatibilities, there are no guarantees that an external parser will work correctly with ESLint and ESLint will not fix bugs related to incompatibilities with other parsers.
-
-To indicate the npm module to use as your parser, specify it using the `parser` option in your `.eslintrc` file. For example, the following specifies to use Esprima instead of Espree:
-
-```json
-{
-    "parser": "esprima",
-    "rules": {
-        "semi": "error"
-    }
-}
-```
-
-The following parsers are compatible with ESLint:
-
-* [Esprima](https://www.npmjs.com/package/esprima)
-* [@babel/eslint-parser](https://www.npmjs.com/package/@babel/eslint-parser) - A wrapper around the [Babel](https://babeljs.io) parser that makes it compatible with ESLint.
-* [@typescript-eslint/parser](https://www.npmjs.com/package/@typescript-eslint/parser) - A parser that converts TypeScript into an ESTree-compatible form so it can be used in ESLint.
-
-Note when using a custom parser, the `parserOptions` configuration property is still required for ESLint to work properly with features not in ECMAScript 5 by default. Parsers are all passed `parserOptions` and may or may not use them to determine which features to enable.
-
-## Specifying Processor
-
-Plugins may provide processors. Processors can extract JavaScript code from another kind of files, then lets ESLint lint the JavaScript code. Or processors can convert JavaScript code in preprocessing for some purpose.
-
-To specify processors in a configuration file, use the `processor` key with the concatenated string of a plugin name and a processor name by a slash. For example, the following enables the processor `a-processor` that the plugin `a-plugin` provided:
-
-```json
-{
-    "plugins": ["a-plugin"],
-    "processor": "a-plugin/a-processor"
-}
-```
-
-To specify processors for specific kinds of files, use the combination of the `overrides` key and the `processor` key. For example, the following uses the processor `a-plugin/markdown` for `*.md` files.
-
-```json
-{
-    "plugins": ["a-plugin"],
-    "overrides": [
-        {
-            "files": ["*.md"],
-            "processor": "a-plugin/markdown"
-        }
-    ]
-}
-```
-
-Processors may make named code blocks such as `0.js` and `1.js`. ESLint handles such a named code block as a child file of the original file. You can specify additional configurations for named code blocks in the `overrides` section of the config. For example, the following disables `strict` rule for the named code blocks which end with `.js` in markdown files.
-
-```json
-{
-    "plugins": ["a-plugin"],
-    "overrides": [
-        {
-            "files": ["*.md"],
-            "processor": "a-plugin/markdown"
-        },
-        {
-            "files": ["**/*.md/*.js"],
-            "rules": {
-                "strict": "off"
-            }
-        }
-    ]
-}
-```
-
-ESLint checks the file path of named code blocks then ignores those if any `overrides` entry didn't match the file path. Be sure to make `overrides` entry if you wanted to lint named code blocks other than `*.js`.
-
-## Specifying Environments
-
-An environment defines global variables that are predefined. The available environments are:
-
-* `browser` - browser global variables.
-* `node` - Node.js global variables and Node.js scoping.
-* `commonjs` - CommonJS global variables and CommonJS scoping (use this for browser-only code that uses Browserify/WebPack).
-* `shared-node-browser` - Globals common to both Node.js and Browser.
-* `es6` - enable all ECMAScript 6 features except for modules (this automatically sets the `ecmaVersion` parser option to 6).
-* `es2017` - adds all ECMAScript 2017 globals and automatically sets the `ecmaVersion` parser option to 8.
-* `es2020` - adds all ECMAScript 2020 globals and automatically sets the `ecmaVersion` parser option to 11.
-* `es2021` - adds all ECMAScript 2021 globals and automatically sets the `ecmaVersion` parser option to 12.
-* `worker` - web workers global variables.
-* `amd` - defines `require()` and `define()` as global variables as per the [amd](https://github.com/amdjs/amdjs-api/wiki/AMD) spec.
-* `mocha` - adds all of the Mocha testing global variables.
-* `jasmine` - adds all of the Jasmine testing global variables for version 1.3 and 2.0.
-* `jest` - Jest global variables.
-* `phantomjs` - PhantomJS global variables.
-* `protractor` - Protractor global variables.
-* `qunit` - QUnit global variables.
-* `jquery` - jQuery global variables.
-* `prototypejs` - Prototype.js global variables.
-* `shelljs` - ShellJS global variables.
-* `meteor` - Meteor global variables.
-* `mongo` - MongoDB global variables.
-* `applescript` - AppleScript global variables.
-* `nashorn` - Java 8 Nashorn global variables.
-* `serviceworker` - Service Worker global variables.
-* `atomtest` - Atom test helper globals.
-* `embertest` - Ember test helper globals.
-* `webextensions` - WebExtensions globals.
-* `greasemonkey` - GreaseMonkey globals.
-
-These environments are not mutually exclusive, so you can define more than one at a time.
-
-Environments can be specified inside of a file, in configuration files or using the `--env` [command line](command-line-interface) flag.
-
-To specify environments using a comment inside of your JavaScript file, use the following format:
-
-```js
-/* eslint-env node, mocha */
-```
-
-This enables Node.js and Mocha environments.
-
-To specify environments in a configuration file, use the `env` key and specify which environments you want to enable by setting each to `true`. For example, the following enables the browser and Node.js environments:
-
-```json
-{
-    "env": {
-        "browser": true,
-        "node": true
-    }
-}
-```
-
-Or in a `package.json` file
-
-```json
-{
-    "name": "mypackage",
-    "version": "0.0.1",
-    "eslintConfig": {
-        "env": {
-            "browser": true,
-            "node": true
-        }
-    }
-}
-```
-
-And in YAML:
-
-```yaml
----
-  env:
-    browser: true
-    node: true
-```
-
-If you want to use an environment from a plugin, be sure to specify the plugin name in the `plugins` array and then use the unprefixed plugin name, followed by a slash, followed by the environment name. For example:
-
-```json
-{
-    "plugins": ["example"],
-    "env": {
-        "example/custom": true
-    }
-}
-```
-
-Or in a `package.json` file
-
-```json
-{
-    "name": "mypackage",
-    "version": "0.0.1",
-    "eslintConfig": {
-        "plugins": ["example"],
-        "env": {
-            "example/custom": true
-        }
-    }
-}
-```
-
-And in YAML:
-
-```yaml
----
-  plugins:
-    - example
-  env:
-    example/custom: true
-```
-
-## Specifying Globals
-
-The [no-undef](../rules/no-undef.md) rule will warn on variables that are accessed but not defined within the same file. If you are using global variables inside of a file then it's worthwhile to define those globals so that ESLint will not warn about their usage. You can define global variables either using comments inside of a file or in the configuration file.
-
-To specify globals using a comment inside of your JavaScript file, use the following format:
-
-```js
-/* global var1, var2 */
-```
-
-This defines two global variables, `var1` and `var2`. If you want to optionally specify that these global variables can be written to (rather than only being read), then you can set each with a `"writable"` flag:
-
-```js
-/* global var1:writable, var2:writable */
-```
-
-To configure global variables inside of a configuration file, set the `globals` configuration property to an object containing keys named for each of the global variables you want to use. For each global variable key, set the corresponding value equal to `"writable"` to allow the variable to be overwritten or `"readonly"` to disallow overwriting. For example:
-
-```json
-{
-    "globals": {
-        "var1": "writable",
-        "var2": "readonly"
-    }
-}
-```
-
-And in YAML:
-
-```yaml
----
-  globals:
-    var1: writable
-    var2: readonly
-```
-
-These examples allow `var1` to be overwritten in your code, but disallow it for `var2`.
-
-Globals can be disabled with the string `"off"`. For example, in an environment where most ES2015 globals are available but `Promise` is unavailable, you might use this config:
-
-```json
-{
-    "env": {
-        "es6": true
-    },
-    "globals": {
-        "Promise": "off"
-    }
-}
-```
-
-For historical reasons, the boolean value `false` and the string value `"readable"` are equivalent to `"readonly"`. Similarly, the boolean value `true` and the string value `"writeable"` are equivalent to `"writable"`. However, the use of older values is deprecated.
-
-**Note:** Enable the [no-global-assign](../rules/no-global-assign.md) rule to disallow modifications to read-only global variables in your code.
-
-## Configuring Plugins
-
-ESLint supports the use of third-party plugins. Before using the plugin, you have to install it using npm.
-
-To configure plugins inside of a configuration file, use the `plugins` key, which contains a list of plugin names. The `eslint-plugin-` prefix can be omitted from the plugin name.
-
-```json
-{
-    "plugins": [
-        "plugin1",
-        "eslint-plugin-plugin2"
-    ]
-}
-```
-
-And in YAML:
-
-```yaml
----
-  plugins:
-    - plugin1
-    - eslint-plugin-plugin2
-```
-
-**Notes:**
-
-1. Plugins are resolved relative to the config file. In other words, ESLint will load the plugin as a user would obtain by running `require('eslint-plugin-pluginname')` in the config file.
-2. Plugins in the base configuration (loaded by `extends` setting) are relative to the derived config file. For example, if `./.eslintrc` has `extends: ["foo"]` and the `eslint-config-foo` has `plugins: ["bar"]`, ESLint finds the `eslint-plugin-bar` from `./node_modules/` (rather than `./node_modules/eslint-config-foo/node_modules/`) or ancestor directories. Thus every plugin in the config file and base configurations is resolved uniquely.
-
-### Naming Convention
-
-#### Include a Plugin
-
-The `eslint-plugin-` prefix can be omitted for non-scoped packages
-
-```js
-{
-    // ...
-    "plugins": [
-        "jquery", // means eslint-plugin-jquery
-    ]
-    // ...
-}
-```
-
-The same rule does apply to scoped packages:
-
-```js
-{
-    // ...
-    "plugins": [
-        "@jquery/jquery", // means @jquery/eslint-plugin-jquery
-        "@foobar" // means @foobar/eslint-plugin
-    ]
-    // ...
-}
-```
-
-#### Use a Plugin
-
-When using rules, environments or configs defined by plugins, they must be referenced following the convention:
-
-* `eslint-plugin-foo` → `foo/a-rule`
-* `@foo/eslint-plugin` → `@foo/a-config`
-* `@foo/eslint-plugin-bar` → `@foo/bar/a-environment`
-
-For example:
-
-```js
-{
-    // ...
-    "plugins": [
-        "jquery",   // eslint-plugin-jquery
-        "@foo/foo", // @foo/eslint-plugin-foo
-        "@bar"      // @bar/eslint-plugin
-    ],
-    "extends": [
-        "plugin:@foo/foo/recommended",
-        "plugin:@bar/recommended"
-    ],
-    "rules": {
-        "jquery/a-rule": "error",
-        "@foo/foo/some-rule": "error",
-        "@bar/another-rule": "error"
-    },
-    "env": {
-        "jquery/jquery": true,
-        "@foo/foo/env-foo": true,
-        "@bar/env-bar": true,
-    }
-    // ...
-}
-```
-
-## Configuring Rules
-
-ESLint comes with a large number of rules. You can modify which rules your project uses either using configuration comments or configuration files. To change a rule setting, you must set the rule ID equal to one of these values:
-
-* `"off"` or `0` - turn the rule off
-* `"warn"` or `1` - turn the rule on as a warning (doesn't affect exit code)
-* `"error"` or `2` - turn the rule on as an error (exit code is 1 when triggered)
-
-### Using Configuration Comments
-
-To configure rules inside of a file using configuration comments, use a comment in the following format:
-
-```js
-/* eslint eqeqeq: "off", curly: "error" */
-```
-
-In this example, [`eqeqeq`](../rules/eqeqeq) is turned off and [`curly`](../rules/curly) is turned on as an error. You can also use the numeric equivalent for the rule severity:
-
-```js
-/* eslint eqeqeq: 0, curly: 2 */
-```
-
-This example is the same as the last example, only it uses the numeric codes instead of the string values. The `eqeqeq` rule is off and the `curly` rule is set to be an error.
-
-If a rule has additional options, you can specify them using array literal syntax, such as:
-
-```js
-/* eslint quotes: ["error", "double"], curly: 2 */
-```
-
-This comment specifies the "double" option for the [`quotes`](../rules/quotes) rule. The first item in the array is always the rule severity (number or string).
-
-Configuration comments can include descriptions to explain why the comment is necessary. The description must occur after the configuration and is separated from the configuration by two or more consecutive `-` characters. For example:
-
-```js
-/* eslint eqeqeq: "off", curly: "error" -- Here's a description about why this configuration is necessary. */
-```
-
-```js
-/* eslint eqeqeq: "off", curly: "error"
-    --------
-    Here's a description about why this configuration is necessary. */
-```
-
-```js
-/* eslint eqeqeq: "off", curly: "error"
- * --------
- * This will not work due to the line above starting with a '*' character.
- */
-```
-
-### Using Configuration Files
-
-To configure rules inside of a configuration file, use the `rules` key along with an error level and any options you want to use. For example:
-
-
-```json
-{
-    "rules": {
-        "eqeqeq": "off",
-        "curly": "error",
-        "quotes": ["error", "double"]
-    }
-}
-```
-
-And in YAML:
-
-```yaml
----
-rules:
-  eqeqeq: off
-  curly: error
-  quotes:
-    - error
-    - double
-```
-
-To configure a rule which is defined within a plugin you have to prefix the rule ID with the plugin name and a `/`. For example:
-
-```json
-{
-    "plugins": [
-        "plugin1"
-    ],
-    "rules": {
-        "eqeqeq": "off",
-        "curly": "error",
-        "quotes": ["error", "double"],
-        "plugin1/rule1": "error"
-    }
-}
-```
-
-And in YAML:
-
-```yaml
----
-plugins:
-  - plugin1
-rules:
-  eqeqeq: 0
-  curly: error
-  quotes:
-    - error
-    - "double"
-  plugin1/rule1: error
-```
-
-In these configuration files, the rule `plugin1/rule1` comes from the plugin named `plugin1`. You can also use this format with configuration comments, such as:
-
-```js
-/* eslint "plugin1/rule1": "error" */
-```
-
-**Note:** When specifying rules from plugins, make sure to omit `eslint-plugin-`. ESLint uses only the unprefixed name internally to locate rules.
-
-## Disabling Rules with Inline Comments
-
-To temporarily disable rule warnings in your file, use block comments in the following format:
-
-```js
-/* eslint-disable */
-
-alert('foo');
-
-/* eslint-enable */
-```
-
-You can also disable or enable warnings for specific rules:
-
-```js
-/* eslint-disable no-alert, no-console */
-
-alert('foo');
-console.log('bar');
-
-/* eslint-enable no-alert, no-console */
-```
-
-To disable rule warnings in an entire file, put a `/* eslint-disable */` block comment at the top of the file:
-
-```js
-/* eslint-disable */
-
-alert('foo');
-```
-
-You can also disable or enable specific rules for an entire file:
-
-```js
-/* eslint-disable no-alert */
-
-alert('foo');
-```
-
-To disable all rules on a specific line, use a line or block comment in one of the following formats:
-
-```js
-alert('foo'); // eslint-disable-line
-
-// eslint-disable-next-line
-alert('foo');
-
-/* eslint-disable-next-line */
-alert('foo');
-
-alert('foo'); /* eslint-disable-line */
-```
-
-To disable a specific rule on a specific line:
-
-```js
-alert('foo'); // eslint-disable-line no-alert
-
-// eslint-disable-next-line no-alert
-alert('foo');
-
-alert('foo'); /* eslint-disable-line no-alert */
-
-/* eslint-disable-next-line no-alert */
-alert('foo');
-```
-
-To disable multiple rules on a specific line:
-
-```js
-alert('foo'); // eslint-disable-line no-alert, quotes, semi
-
-// eslint-disable-next-line no-alert, quotes, semi
-alert('foo');
-
-alert('foo'); /* eslint-disable-line no-alert, quotes, semi */
-
-/* eslint-disable-next-line no-alert, quotes, semi */
-alert('foo');
-```
-
-All of the above methods also work for plugin rules. For example, to disable `eslint-plugin-example`'s `rule-name` rule, combine the plugin's name (`example`) and the rule's name (`rule-name`) into `example/rule-name`:
-
-```js
-foo(); // eslint-disable-line example/rule-name
-foo(); /* eslint-disable-line example/rule-name */
-```
-
-Configuration comments can include descriptions to explain why the comment is necessary. The description must occur after the configuration and is separated from the configuration by two or more consecutive `-` characters. For example:
-
-```js
-// eslint-disable-next-line no-console -- Here's a description about why this configuration is necessary.
-console.log('hello');
-```
-
-**Note:** Comments that disable warnings for a portion of a file tell ESLint not to report rule violations for the disabled code. ESLint still parses the entire file, however, so disabled code still needs to be syntactically valid JavaScript.
-
-
-### Disabling Rules Only for a Group of Files
-
-To disable rules inside of a configuration file for a group of files, use the `overrides` key along with a `files` key. For example:
-
-```json
-{
-  "rules": {...},
-  "overrides": [
-    {
-      "files": ["*-test.js","*.spec.js"],
-      "rules": {
-        "no-unused-expressions": "off"
-      }
-    }
-  ]
-}
-```
-
-## Configuring Inline Comment Behaviors
-
-### Disabling Inline Comments
-
-To disable all inline config comments, use `noInlineConfig` setting. For example:
-
-```json
-{
-  "rules": {...},
-  "noInlineConfig": true
-}
-```
-
-This setting is similar to [--no-inline-config](./command-line-interface.md#--no-inline-config) CLI option.
-
-### Report Unused `eslint-disable` Comments
-
-To report unused `eslint-disable` comments, use `reportUnusedDisableDirectives` setting. For example:
-
-```json
-{
-  "rules": {...},
-  "reportUnusedDisableDirectives": true
-}
-```
-
-This setting is similar to [--report-unused-disable-directives](./command-line-interface.md#--report-unused-disable-directives) CLI option, but doesn't fail linting (reports as `"warn"` severity).
-
-## Adding Shared Settings
-
-ESLint supports adding shared settings into configuration file. You can add `settings` object to ESLint configuration file and it will be supplied to every rule that will be executed. This may be useful if you are adding custom rules and want them to have access to the same information and be easily configurable.
-
-In JSON:
-
-```json
-{
-    "settings": {
-        "sharedData": "Hello"
-    }
-}
-```
-
-And in YAML:
-
-```yaml
----
-  settings:
-    sharedData: "Hello"
-```
-
-## Using Configuration Files
-
-There are two ways to use configuration files.
-
-The first way to use configuration files is via `.eslintrc.*` and `package.json` files. ESLint will automatically look for them in the directory of the file to be linted, and in successive parent directories all the way up to the root directory of the filesystem (unless `root: true` is specified). This option is useful when you want different configurations for different parts of a project or when you want others to be able to use ESLint directly without needing to remember to pass in the configuration file.
-
-The second is to save the file wherever you would like and pass its location to the CLI using the `-c` option, such as:
-
-    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` along with the `-c` flag.
-
-In each case, the settings in the configuration file override default settings.
-
-## Configuration File Formats
-
-ESLint supports configuration files in several formats:
-
-* **JavaScript** - use `.eslintrc.js` and export an object containing your configuration.
-* **JavaScript (ESM)** - use `.eslintrc.cjs` when running ESLint in JavaScript packages that specify `"type":"module"` in their `package.json`. Note that ESLint does not support ESM configuration at this time.
-* **YAML** - use `.eslintrc.yaml` or `.eslintrc.yml` to define the configuration structure.
-* **JSON** - use `.eslintrc.json` to define the configuration structure. ESLint's JSON files also allow JavaScript-style comments.
-* **Deprecated** - use `.eslintrc`, which can be either JSON or YAML.
-* **package.json** - create an `eslintConfig` property in your `package.json` file and define your configuration there.
-
-If there are multiple configuration files in the same directory, ESLint will only use one. The priority order is:
-
-1. `.eslintrc.js`
-1. `.eslintrc.cjs`
-1. `.eslintrc.yaml`
-1. `.eslintrc.yml`
-1. `.eslintrc.json`
-1. `.eslintrc`
-1. `package.json`
-
-## Configuration Cascading and Hierarchy
-
-When using `.eslintrc.*` and `package.json` files for configuration, you can take advantage of configuration cascading. For instance, suppose you have the following structure:
-
-```text
-your-project
-├── .eslintrc
-├── lib
-│ └── source.js
-└─┬ tests
-  ├── .eslintrc
-  └── test.js
-```
-
-The configuration cascade works by using the closest `.eslintrc` file to the file being linted as the highest priority, then any configuration files in the parent directory, and so on. When you run ESLint on this project, all files in `lib/` will use the `.eslintrc` file at the root of the project as their configuration. When ESLint traverses into the `tests/` directory, it will then use `your-project/tests/.eslintrc` in addition to `your-project/.eslintrc`. So `your-project/tests/test.js` is linted based on the combination of the two `.eslintrc` files in its directory hierarchy, with the closest one taking priority. In this way, you can have project-level ESLint settings and also have directory-specific overrides.
-
-In the same way, if there is a `package.json` file in the root directory with an `eslintConfig` field, the configuration it describes will apply to all subdirectories beneath it, but the configuration described by the `.eslintrc` file in the tests directory will override it where there are conflicting specifications.
-
-```text
-your-project
-├── package.json
-├── lib
-│ └── source.js
-└─┬ tests
-  ├── .eslintrc
-  └── test.js
-```
-
-If there is an `.eslintrc` and a `package.json` file found in the same directory, `.eslintrc` will take a priority and `package.json` file will not be used.
-
-By default, ESLint will look for configuration files in all parent folders up to the root directory. This can be useful if you want all of your projects to follow a certain convention, but can sometimes lead to unexpected results. To limit ESLint to a specific project, place `"root": true` inside the `eslintConfig` field of the `package.json` file or in the `.eslintrc.*` file at your project's root level.  ESLint will stop looking in parent folders once it finds a configuration with `"root": true`.
-
-```js
-{
-    "root": true
-}
-```
-
-And in YAML:
-
-```yaml
----
-  root: true
-```
-
-For example, consider `projectA` which has `"root": true` set in the `.eslintrc` file in the `lib/` directory.  In this case, while linting `main.js`, the configurations within `lib/` will be used, but the `.eslintrc` file in `projectA/` will not.
-
-```text
-home
-└── user
-    └── projectA
-        ├── .eslintrc  <- Not used
-        └── lib
-            ├── .eslintrc  <- { "root": true }
-            └── main.js
-```
-
-The complete configuration hierarchy, from highest precedence to lowest precedence, is as follows:
-
-1. Inline configuration
-    1. `/*eslint-disable*/` and `/*eslint-enable*/`
-    1. `/*global*/`
-    1. `/*eslint*/`
-    1. `/*eslint-env*/`
-1. Command line options (or CLIEngine equivalents):
-    1. `--global`
-    1. `--rule`
-    1. `--env`
-    1. `-c`, `--config`
-1. Project-level configuration:
-    1. `.eslintrc.*` or `package.json` file in same directory as linted file
-    1. Continue searching for `.eslintrc` and `package.json` files in ancestor directories (parent has highest precedence, then grandparent, etc.), up to and including the root directory or until a config with `"root": true` is found.
-
-## Extending Configuration Files
-
-A configuration file can extend the set of enabled rules from base configurations.
-
-The `extends` property value is either:
-
-* a string that specifies a configuration (either a path to a config file, the name of a shareable config, `eslint:recommended`, or `eslint:all`)
-* an array of strings: each additional configuration extends the preceding configurations
-
-ESLint extends configurations recursively, so a base configuration can also have an `extends` property. Relative paths and shareable config names in an `extends` property are resolved from the location of the config file where they appear.
-
-The `rules` property can do any of the following to extend (or override) the set of rules:
-
-* enable additional rules
-* change an inherited rule's severity without changing its options:
-    * Base config: `"eqeqeq": ["error", "allow-null"]`
-    * Derived config: `"eqeqeq": "warn"`
-    * Resulting actual config: `"eqeqeq": ["warn", "allow-null"]`
-* override options for rules from base configurations:
-    * Base config: `"quotes": ["error", "single", "avoid-escape"]`
-    * Derived config: `"quotes": ["error", "single"]`
-    * Resulting actual config: `"quotes": ["error", "single"]`
-
-### Using `"eslint:recommended"`
-
-An `extends` property value `"eslint:recommended"` enables a subset of core rules that report common problems, which have a check mark (recommended) on the [rules page](../rules/). The recommended subset can change only at major versions of ESLint.
-
-If your configuration extends the recommended rules: after you upgrade to a newer major version of ESLint, review the reported problems before you use the `--fix` option on the [command line](./command-line-interface#fix), so you know if a new fixable recommended rule will make changes to the code.
-
-The `eslint --init` command can create a configuration so you can extend the recommended rules.
-
-Example of a configuration file in JavaScript format:
-
-```js
-module.exports = {
-    "extends": "eslint:recommended",
-    "rules": {
-        // enable additional rules
-        "indent": ["error", 4],
-        "linebreak-style": ["error", "unix"],
-        "quotes": ["error", "double"],
-        "semi": ["error", "always"],
-
-        // override default options for rules from base configurations
-        "comma-dangle": ["error", "always"],
-        "no-cond-assign": ["error", "always"],
-
-        // disable rules from base configurations
-        "no-console": "off",
-    }
-}
-```
-
-### Using a shareable configuration package
-
-A [sharable configuration](../developer-guide/shareable-configs) is an npm package that exports a configuration object. Make sure the package has been installed to a directory where ESLint can require it.
-
-The `extends` property value can omit the `eslint-config-` prefix of the package name.
-
-The `eslint --init` command can create a configuration so you can extend a popular style guide (for example, `eslint-config-standard`).
-
-Example of a configuration file in YAML format:
-
-```yaml
-extends: standard
-rules:
-  comma-dangle:
-    - error
-    - always
-  no-empty: warn
-```
-
-### Using the configuration from a plugin
-
-A [plugin](../developer-guide/working-with-plugins) is an npm package that usually exports rules. Some plugins also export one or more named [configurations](../developer-guide/working-with-plugins#configs-in-plugins). Make sure the package has been installed to a directory where ESLint can require it.
-
-The `plugins` [property value](#configuring-plugins) can omit the `eslint-plugin-` prefix of the package name.
-
-The `extends` property value can consist of:
-
-* `plugin:`
-* the package name (from which you can omit the prefix, for example, `react`)
-* `/`
-* the configuration name (for example, `recommended`)
-
-Example of a configuration file in JSON format:
-
-```json
-{
-    "plugins": [
-        "react"
-    ],
-    "extends": [
-        "eslint:recommended",
-        "plugin:react/recommended"
-    ],
-    "rules": {
-       "react/no-set-state": "off"
-    }
-}
-```
-
-### Using a configuration file
-
-The `extends` property value can be an absolute or relative path to a base [configuration file](#using-configuration-files). ESLint resolves a relative path to a base configuration file relative to the configuration file that uses it.
-
-Example of a configuration file in JSON format:
-
-```json
-{
-    "extends": [
-        "./node_modules/coding-standard/eslintDefaults.js",
-        "./node_modules/coding-standard/.eslintrc-es6",
-        "./node_modules/coding-standard/.eslintrc-jsx"
-    ],
-    "rules": {
-        "eqeqeq": "warn"
-    }
-}
-```
-
-### Using `"eslint:all"`
-
-The `extends` property value can be `"eslint:all"` to enable all core rules in the currently installed version of ESLint. The set of core rules can change at any minor or major version of ESLint.
-
-**Important:** This configuration is **not recommended for production use** because it changes with every minor and major version of ESLint. Use at your own risk.
-
-If you configure ESLint to automatically enable new rules when you upgrade, ESLint can report new problems when there are no changes to source code, therefore any newer minor version of ESLint can behave as if it has breaking changes.
-
-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` rule does not mean double quotes are better than single quotes).
-
-If your configuration extends all core rules: after you upgrade to a newer major or minor version of ESLint, review the reported problems before you use the `--fix` option on the [command line](./command-line-interface#fix), so you know if a new fixable rule will make changes to the code.
-
-Example of a configuration file in JavaScript format:
-
-```js
-module.exports = {
-    "extends": "eslint:all",
-    "rules": {
-        // override default options
-        "comma-dangle": ["error", "always"],
-        "indent": ["error", 2],
-        "no-cond-assign": ["error", "always"],
-
-        // disable now, but enable in the future
-        "one-var": "off", // ["error", "never"]
-
-        // disable
-        "init-declarations": "off",
-        "no-console": "off",
-        "no-inline-comments": "off",
-    }
-}
-```
-
-## Configuration Based on Glob Patterns
-
-<b>v4.1.0+.</b> Sometimes a more fine-controlled configuration is necessary, for example if the configuration for files within the same directory has to be different. Therefore you can provide configurations under the `overrides` key that will only apply to files that match specific glob patterns, using the same format you would pass on the command line (e.g., `app/**/*.test.js`).
-
-### How it works
-
-* The patterns are applied against the file path relative to the directory of the config file. For example, if your config file has the path `/Users/john/workspace/any-project/.eslintrc.js` and the file you want to lint has the path `/Users/john/workspace/any-project/lib/util.js`, then the pattern provided in `.eslintrc.js` will be executed against the relative path `lib/util.js`.
-* Glob pattern overrides have higher precedence than the regular configuration in the same config file. Multiple overrides within the same config are applied in order. That is, the last override block in a config file always has the highest precedence.
-* A glob specific configuration works almost the same as any other ESLint config. Override blocks can contain any configuration options that are valid in a regular config, with the exception of `root` and `ignorePatterns`.
-    * A glob specific configuration can have `extends` setting, but the `root` property in the extended configs is ignored. The `ignorePatterns` property in the extended configs is used only for the files the glob specific configuration matched.
-    * Nested `overrides` setting will be applied only if the glob patterns of both of the parent config and the child config matched. This is the same when the extended configs have `overrides` setting.
-* Multiple glob patterns can be provided within a single override block. A file must match at least one of the supplied patterns for the configuration to apply.
-* Override blocks can also specify patterns to exclude from matches. If a file matches any of the excluded patterns, the configuration won't apply.
-
-### Relative glob patterns
-
-```
-project-root
-├── app
-│   ├── lib
-│   │   ├── foo.js
-│   │   ├── fooSpec.js
-│   ├── components
-│   │   ├── bar.js
-│   │   ├── barSpec.js
-│   ├── .eslintrc.json
-├── server
-│   ├── server.js
-│   ├── serverSpec.js
-├── .eslintrc.json
-```
-
-The config in `app/.eslintrc.json` defines the glob pattern `**/*Spec.js`. This pattern is relative to the base directory of `app/.eslintrc.json`. So, this pattern would match `app/lib/fooSpec.js` and `app/components/barSpec.js` but **NOT** `server/serverSpec.js`. If you defined the same pattern in the `.eslintrc.json` file within in the `project-root` folder, it would match all three of the `*Spec` files.
-
-If a config is provided via the `--config` CLI option, the glob patterns in the config are relative to the current working directory rather than the base directory of the given config. For example, if `--config configs/.eslintrc.json` is present, the glob patterns in the config are relative to `.` rather than `./configs`.
-
-### Example configuration
-
-In your `.eslintrc.json`:
-
-```json
-{
-  "rules": {
-    "quotes": ["error", "double"]
-  },
-
-  "overrides": [
-    {
-      "files": ["bin/*.js", "lib/*.js"],
-      "excludedFiles": "*.test.js",
-      "rules": {
-        "quotes": ["error", "single"]
-      }
-    }
-  ]
-}
-```
-
-### Specifying Target Files to Lint
-
-If you specified directories with CLI (e.g., `eslint lib`), ESLint searches target files in the directory to lint. The target files are `*.js` or the files that match any of `overrides` entries (but exclude entries that are any of `files` end with `*`).
-
-If you specified the [`--ext`](./command-line-interface#ext) command line option along with directories, the target files are only the files that have specified file extensions regardless of `overrides` entries.
-
-## Comments in Configuration Files
-
-Both the JSON and YAML configuration file formats support comments (`package.json` files should not include them). You can use JavaScript-style comments or YAML-style comments in either type of file and ESLint will safely ignore them. This allows your configuration files to be more human-friendly. For example:
-
-```js
-{
-    "env": {
-        "browser": true
-    },
-    "rules": {
-        // Override our default settings just for this directory
-        "eqeqeq": "warn",
-        "strict": "off"
-    }
-}
-```
-
-## Ignoring Files and Directories
-
-### `ignorePatterns` in config files
-
-You can tell ESLint to ignore specific files and directories by `ignorePatterns` in your config files. Each value of `ignorePatterns` is the same pattern as each line of `.eslintignore` in the next section.
-
-```json
-{
-    "ignorePatterns": ["temp.js", "**/vendor/*.js"],
-    "rules": {
-        //...
-    }
-}
-```
-
-* The `ignorePatterns` property affects only the directory that the config file placed.
-* You cannot write `ignorePatterns` property under `overrides` property.
-* `.eslintignore` can override `ignorePatterns` property of config files.
-
-If a glob pattern starts with `/`, the pattern is relative to the base directory of the config file. For example, `/foo.js` in `lib/.eslintrc.json` matches to `lib/foo.js` but not `lib/subdir/foo.js`.
-
-If a config is provided via the `--config` CLI option, the ignore patterns that start with `/` in the config are relative to the current working directory rather than the base directory of the given config. For example, if `--config configs/.eslintrc.json` is present, the ignore patterns in the config are relative to `.` rather than `./configs`.
-
-### `.eslintignore`
-
-You can tell ESLint to ignore specific files and directories by creating an `.eslintignore` file in your project's root directory. The `.eslintignore` file is a plain text file where each line is a glob pattern indicating which paths should be omitted from linting. For example, the following will omit all JavaScript files:
-
-```text
-**/*.js
-```
-
-When ESLint is run, it looks in the current working directory to find an `.eslintignore` file before determining which files to lint. If this file is found, then those preferences are applied when traversing directories. Only one `.eslintignore` file can be used at a time, so `.eslintignore` files other than the one in the current working directory will not be used.
-
-Globs are matched using [node-ignore](https://github.com/kaelzhang/node-ignore), so a number of features are available:
-
-* Lines beginning with `#` are treated as comments and do not affect ignore patterns.
-* Paths are relative to the current working directory. This is also true of paths passed in via the `--ignore-pattern` [command](./command-line-interface.md#--ignore-pattern).
-* Lines preceded by `!` are negated patterns that re-include a pattern that was ignored by an earlier pattern.
-* Ignore patterns behave according to the `.gitignore` [specification](https://git-scm.com/docs/gitignore).
-
-Of particular note is that like `.gitignore` files, all paths used as patterns for both `.eslintignore` and `--ignore-pattern` must use forward slashes as their path separators.
-
-```text
-# Valid
-/root/src/*.js
-
-# Invalid
-\root\src\*.js
-```
-
-Please see `.gitignore`'s specification for further examples of valid syntax.
-
-In addition to any patterns in the `.eslintignore` file, ESLint always follows a couple implicit ignore rules even if the `--no-ignore` flag is passed. The implicit rules are as follows:
-
-* `node_modules/` is ignored.
-* Dotfiles (except for `.eslintrc.*`) as well as Dotfolders and their contents are ignored.
-
-There are also some exceptions to these rules:
-
-* If the path to lint is a glob pattern or directory path and contains a Dotfolder, all Dotfiles and Dotfolders will be linted. This includes sub-dotfiles and sub-dotfolders that are buried deeper in the directory structure.
-
-  For example, `eslint .config/` will lint all Dotfolders and Dotfiles in the `.config` directory, including immediate children as well as children that are deeper in the directory structure.
-
-* If the path to lint is a specific file path and the `--no-ignore` flag has been passed, ESLint will lint the file regardless of the implicit ignore rules.
-
-  For example, `eslint .config/my-config-file.js --no-ignore` will cause `my-config-file.js` to be linted. It should be noted that the same command without the `--no-ignore` line will not lint the `my-config-file.js` file.
-
-* Allowlist and denylist rules specified via `--ignore-pattern` or `.eslintignore` are prioritized above implicit ignore rules.
-
-  For example, in this scenario, `.build/test.js` is the desired file to allowlist. Because all Dotfolders and their children are ignored by default, `.build` must first be allowlisted so that eslint because aware of its children. Then, `.build/test.js` must be explicitly allowlisted, while the rest of the content is denylisted. This is done with the following `.eslintignore` file:
-
-  ```text
-  # Allowlist 'test.js' in the '.build' folder
-  # But do not allow anything else in the '.build' folder to be linted
-  !.build
-  .build/*
-  !.build/test.js
-  ```
-
-  The following `--ignore-pattern` is also equivalent:
-
-      eslint --ignore-pattern '!.build' --ignore-pattern '.build/*' --ignore-pattern '!.build/test.js' parent-folder/
-
-### Using an Alternate File
-
-If you'd prefer to use a different file than the `.eslintignore` in the current working directory, you can specify it on the command line using the `--ignore-path` option. For example, you can use `.jshintignore` file because it has the same format:
-
-    eslint --ignore-path .jshintignore file.js
-
-You can also use your `.gitignore` file:
-
-    eslint --ignore-path .gitignore file.js
-
-Any file that follows the standard ignore file format can be used. Keep in mind that specifying `--ignore-path` means that any existing `.eslintignore` file will not be used. Note that globbing rules in `.eslintignore` follow those of `.gitignore`.
-
-### Using eslintIgnore in package.json
-
-If an `.eslintignore` file is not found and an alternate file is not specified, ESLint will look in package.json for an `eslintIgnore` key to check for files to ignore.
-
-    {
-      "name": "mypackage",
-      "version": "0.0.1",
-      "eslintConfig": {
-          "env": {
-              "browser": true,
-              "node": true
-          }
-      },
-      "eslintIgnore": ["hello.js", "world.js"]
-    }
-
-### Ignored File Warnings
-
-When you pass directories to ESLint, files and directories are silently ignored. If you pass a specific file to ESLint, then you will see a warning indicating that the file was skipped. For example, suppose you have an `.eslintignore` file that looks like this:
-
-```text
-foo.js
-```
-
-And then you run:
-
-    eslint foo.js
-
-You'll see this warning:
-
-```text
-foo.js
-  0:0  warning  File ignored because of a matching ignore pattern. Use "--no-ignore" to override.
-
-✖ 1 problem (0 errors, 1 warning)
-```
-
-This message occurs because ESLint is unsure if you wanted to actually lint the file or not. As the message indicates, you can use `--no-ignore` to omit using the ignore rules.
-
-Consider another scenario where you may want to run ESLint on a specific Dotfile or Dotfolder, but have forgotten to specifically allow those files in your `.eslintignore` file. You would run something like this:
-
-    eslint .config/foo.js
-
-You would see this warning:
-
-```text
-.config/foo.js
-  0:0  warning  File ignored by default.  Use a negated ignore pattern (like "--ignore-pattern '!<relative/path/to/filename>'") to override
-
-✖ 1 problem (0 errors, 1 warning)
-```
-
-This messages occurs because, normally, this file would be ignored by ESLint's implicit ignore rules (as mentioned above). A negated ignore rule in your `.eslintignore` file would override the implicit rule and reinclude this file for linting. Additionally, in this specific case, `--no-ignore` could be used to lint the file as well.
-
-## Personal Configuration File (deprecated)
-
-⚠️ **This feature has been deprecated**. This feature will be removed in the 8.0.0 release. If you want to continue to use personal configuration files, please use the [`--config` CLI option](https://eslint.org/docs/user-guide/command-line-interface#-c---config). For more information regarding this decision, please see [RFC 28](https://github.com/eslint/rfcs/pull/28) and [RFC 32](https://github.com/eslint/rfcs/pull/32).
-
-`~/` refers to [the home directory of the current user on your preferred operating system](https://nodejs.org/api/os.html#os_os_homedir). The personal configuration file being referred to here is `~/.eslintrc.*` file, which is currently handled differently than other configuration files.
-
-### How ESLint Finds Personal Configuration File
-
-If `eslint` could not find any configuration file in the project, `eslint` loads `~/.eslintrc.*` file.
-
-If `eslint` could find configuration files in the project, `eslint` ignores `~/.eslintrc.*` file even if it's in an ancestor directory of the project directory.
-
-### How Personal Configuration File Behaves
-
-`~/.eslintrc.*` files behave similarly to regular configuration files, with some exceptions:
-
-`~/.eslintrc.*` files load shareable configs and custom parsers from `~/node_modules/` – similarly to `require()` – in the user's home directory. Please note that it doesn't load global-installed packages.
-
-`~/.eslintrc.*` files load plugins from `$CWD/node_modules` by default in order to identify plugins uniquely. If you want to use plugins with `~/.eslintrc.*` files, plugins must be installed locally per project. Alternatively, you can use the [`--resolve-plugins-relative-to` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--resolve-plugins-relative-to) to change the location from which ESLint loads plugins.
diff --git a/eslint/docs/user-guide/configuring/README.md b/eslint/docs/user-guide/configuring/README.md
new file mode 100644 (file)
index 0000000..2d22421
--- /dev/null
@@ -0,0 +1,52 @@
+# Configuring ESLint
+
+ESLint is designed to be flexible and configurable for your use case. You can turn off every rule and run only with basic syntax validation or mix and match the bundled rules and your custom rules to fit the needs of your project. There are two primary ways to configure ESLint:
+
+1. **Configuration Comments** - use JavaScript comments to embed configuration information directly into a file.
+1. **Configuration Files** - use a JavaScript, JSON, or YAML file to specify configuration information for an entire directory and all of its subdirectories. This can be in the form of an [`.eslintrc.*`](./configuration-files.md#configuration-file-formats) file or an `eslintConfig` field in a [`package.json`](https://docs.npmjs.com/files/package.json) file, both of which ESLint will look for and read automatically, or you can specify a configuration file on the [command line](https://eslint.org/docs/user-guide/command-line-interface).
+
+Here are some of the options that you can configure in ESLint:
+
+* [**Environments**](./language-options.md#specifying-environments) - which environments your script is designed to run in. Each environment brings with it a certain set of predefined global variables.
+* [**Globals**](./language-options.md#specifying-globals) - the additional global variables your script accesses during execution.
+* [**Rules**](rules.md) - which rules are enabled and at what error level.
+* [**Plugins**](plugins.md) - which third-party plugins define additional rules, environments, configs, etc. for ESLint to use.
+
+All of these options give you fine-grained control over how ESLint treats your code.
+
+## Table of Contents
+
+[**Configuration Files**](configuration-files.md)
+
+* [Configuration File Formats](./configuration-files.md#configuration-file-formats)
+* [Using Configuration Files](./configuration-files.md#using-configuration-files)
+* [Adding Shared Settings](./configuration-files.md#adding-shared-settings)
+* [Cascading and Hierarchy](./configuration-files.md#cascading-and-hierarchy)
+* [Extending Configuration Files](./configuration-files.md#extending-configuration-files)
+* [Configuration Based on Glob Patterns](./configuration-files.md#configuration-based-on-glob-patterns)
+* [Personal Configuration Files](./configuration-files.md#personal-configuration-files-deprecated)
+
+[**Language Options**](language-options.md)
+
+* [Specifying Environments](./language-options.md#specifying-environments)
+* [Specifying Globals](./language-options.md#specifying-globals)
+* [Specifying Parser Options](./language-options.md#specifying-parser-options)
+
+[**Rules**](rules.md)
+
+* [Configuring Rules](./rules.md#configuring-rules)
+* [Disabling Rules](./rules.md#disabling-rules)
+
+[**Plugins**](plugins.md)
+
+* [Specifying Parser](./plugins.md#specifying-parser)
+* [Specifying Processor](./plugins.md#specifying-processor)
+* [Configuring Plugins](./plugins.md#configuring-plugins)
+
+[**Ignoring Code**](ignoring-code.md)
+
+* [`ignorePatterns` in Config Files](./ignoring-code.md#ignorepatterns-in-config-files)
+* [The `.eslintignore` File](./ignoring-code.md#the-eslintignore-file)
+* [Using an Alternate File](./ignoring-code.md#using-an-alternate-file)
+* [Using eslintIgnore in package.json](./ignoring-code.md#using-eslintignore-in-packagejson)
+* [Ignored File Warnings](./ignoring-code.md#ignored-file-warnings)
diff --git a/eslint/docs/user-guide/configuring/configuration-files.md b/eslint/docs/user-guide/configuring/configuration-files.md
new file mode 100644 (file)
index 0000000..e8aa6bf
--- /dev/null
@@ -0,0 +1,408 @@
+# Configuration Files
+
+* [Configuration File Formats](#configuration-file-formats)
+* [Using Configuration Files](#using-configuration-files)
+* [Adding Shared Settings](#adding-shared-settings)
+* [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)
+
+## Configuration File Formats
+
+ESLint supports configuration files in several formats:
+
+* **JavaScript** - use `.eslintrc.js` and export an object containing your configuration.
+* **JavaScript (ESM)** - use `.eslintrc.cjs` when running ESLint in JavaScript packages that specify `"type":"module"` in their `package.json`. Note that ESLint does not support ESM configuration at this time.
+* **YAML** - use `.eslintrc.yaml` or `.eslintrc.yml` to define the configuration structure.
+* **JSON** - use `.eslintrc.json` to define the configuration structure. ESLint's JSON files also allow JavaScript-style comments.
+* **package.json** - create an `eslintConfig` property in your `package.json` file and define your configuration there.
+
+If there are multiple configuration files in the same directory, ESLint will only use one. The priority order is as follows:
+
+1. `.eslintrc.js`
+1. `.eslintrc.cjs`
+1. `.eslintrc.yaml`
+1. `.eslintrc.yml`
+1. `.eslintrc.json`
+1. `package.json`
+
+## Using Configuration Files
+
+There are two ways to use configuration files.
+
+The first way to use configuration files is via `.eslintrc.*` and `package.json` files. ESLint will automatically look for them in the directory of the file to be linted, and in successive parent directories all the way up to the root directory of the filesystem (unless `root: true` is specified). Configuration files can be useful when you want different configurations for different parts of a project or when you want others to be able to use ESLint directly without needing to remember to pass in the configuration file.
+
+The second way to use configuration files is to save the file wherever you would like and pass its location to the CLI using the `--config` option, such as:
+
+    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.
+
+### Comments in configuration files
+
+Both the JSON and YAML configuration file formats support comments (package.json files should not include them). You can use JavaScript-style comments for JSON files and YAML-style comments for YAML files. ESLint safely ignores comments in configuration files. This allows your configuration files to be more human-friendly.
+
+For JavaScript-style comments:
+
+```js
+{
+    "env": {
+        "browser": true
+    },
+    "rules": {
+        // Override our default settings just for this directory
+        "eqeqeq": "warn",
+        "strict": "off"
+    }
+}
+```
+
+For YAML-style comments:
+
+```yaml
+env:
+    browser: true
+rules:
+    # Override default settings
+    eqeqeq: warn
+    strict: off
+```
+
+## Adding Shared Settings
+
+ESLint supports adding shared settings into configuration files. Plugins use `settings` to specify information that should be shared across all of its rules. You can add `settings` object to ESLint configuration file and it will be supplied to every rule being executed. This may be useful if you are adding custom rules and want them to have access to the same information and be easily configurable.
+
+In JSON:
+
+```json
+{
+    "settings": {
+        "sharedData": "Hello"
+    }
+}
+```
+
+And in YAML:
+
+```yaml
+---
+  settings:
+    sharedData: "Hello"
+```
+
+## Cascading and Hierarchy
+
+When using `.eslintrc.*` and `package.json` files for configuration, you can take advantage of configuration cascading. Suppose you have the following structure:
+
+```text
+your-project
+├── .eslintrc.json
+├── lib
+│ └── source.js
+└─┬ tests
+  ├── .eslintrc.json
+  └── test.js
+```
+
+The configuration cascade works based on the location of the file being linted. If there is a `.eslintrc` file in the same directory as the file being linted, then that configuration takes precedence. ESLint then searches up the directory structure, merging any `.eslintrc` files it finds along the way until reaching either a `.eslintrc` file with `root: true` or the root directory.
+
+In the same way, if there is a `package.json` file in the root directory with an `eslintConfig` field, the configuration it describes will apply to all subdirectories beneath it, but the configuration described by the `.eslintrc` file in the `tests/` directory will override it where there are conflicting specifications.
+
+```text
+your-project
+├── package.json
+├── lib
+│ └── source.js
+└─┬ tests
+  ├── .eslintrc.json
+  └── test.js
+```
+
+If there is an `.eslintrc` and a `package.json` file found in the same directory, `.eslintrc` will take priority and `package.json` file will not be used.
+
+By default, ESLint will look for configuration files in all parent folders up to the root directory. This can be useful if you want all of your projects to follow a certain convention, but can sometimes lead to unexpected results. To limit ESLint to a specific project, place `"root": true` inside the `.eslintrc.*` file or `eslintConfig` field of the `package.json` file or in the `.eslintrc.*` file at your project's root level. ESLint will stop looking in parent folders once it finds a configuration with `"root": true`.
+
+```js
+{
+    "root": true
+}
+```
+
+And in YAML:
+
+```yaml
+---
+  root: true
+```
+
+For example, consider `projectA` which has `"root": true` set in the `.eslintrc` file in the `lib/` directory.  In this case, while linting `main.js`, the configurations within `lib/` will be used, but the `.eslintrc` file in `projectA/` will not.
+
+```text
+home
+└── user
+    └── projectA
+        ├── .eslintrc.json  <- Not used
+        └── lib
+            ├── .eslintrc.json  <- { "root": true }
+            └── main.js
+```
+
+The complete configuration hierarchy, from highest to lowest precedence, is as follows:
+
+1. Inline configuration
+    1. `/*eslint-disable*/` and `/*eslint-enable*/`
+    1. `/*global*/`
+    1. `/*eslint*/`
+    1. `/*eslint-env*/`
+1. Command line options (or CLIEngine equivalents):
+    1. `--global`
+    1. `--rule`
+    1. `--env`
+    1. `-c`, `--config`
+1. Project-level configuration:
+    1. `.eslintrc.*` or `package.json` file in the same directory as the linted file
+    1. Continue searching for `.eslintrc.*` and `package.json` files in ancestor directories up to and including the root directory or until a config with `"root": true` is found.
+
+## Extending Configuration Files
+
+A configuration file, once extended, can inherit all the traits of another configuration file (including rules, plugins, and language options) and modify all the options. As a result, there are three configurations, as defined below:
+
+* Base config: the configuration that is extended.
+* Derived config: the configuration that extends the base configuration.
+* Resulting actual config: the result of merging the derived configuration into the base configuration.
+
+The `extends` property value is either:
+
+* a string that specifies a configuration (either a path to a config file, the name of a shareable config, `eslint:recommended`, or `eslint:all`)
+* an array of strings where each additional configuration extends the preceding configurations
+
+ESLint extends configurations recursively, so a base configuration can also have an `extends` property. Relative paths and shareable config names in an `extends` property are resolved from the location of the config file where they appear.
+
+The `eslint-config-` prefix can be omitted from the configuration name. For example, `airbnb` resolves as `eslint-config-airbnb`.
+
+The `rules` property can do any of the following to extend (or override) the set of rules:
+
+* enable additional rules
+* change an inherited rule's severity without changing its options:
+    * Base config: `"eqeqeq": ["error", "allow-null"]`
+    * Derived config: `"eqeqeq": "warn"`
+    * Resulting actual config: `"eqeqeq": ["warn", "allow-null"]`
+* override options for rules from base configurations:
+    * Base config: `"quotes": ["error", "single", "avoid-escape"]`
+    * Derived config: `"quotes": ["error", "single"]`
+    * Resulting actual config: `"quotes": ["error", "single"]`
+
+### Using a shareable configuration package
+
+A [sharable configuration](https://eslint.org/docs/developer-guide/shareable-configs) is an npm package that exports a configuration object. Make sure that you have installed the package in your project root directory, so that ESLint can require it.
+
+The `extends` property value can omit the `eslint-config-` prefix of the package name.
+
+The `eslint --init` command can create a configuration so you can extend a popular style guide (for example, `eslint-config-standard`).
+
+Example of a configuration file in YAML format:
+
+```yaml
+extends: standard
+rules:
+  comma-dangle:
+    - error
+    - always
+  no-empty: warn
+```
+
+### Using `eslint:recommended`
+
+Using `"eslint:recommended"` in the `extends` property enables a subset of core rules that report common problems (these rules are identified with a checkmark (recommended) on the [rules page](https://eslint.org/docs/rules/)).
+
+Here's an example of extending `eslint:recommended` and overriding some of the set configuration options:
+
+Example of a configuration file in JavaScript format:
+
+```js
+module.exports = {
+    "extends": "eslint:recommended",
+    "rules": {
+        // enable additional rules
+        "indent": ["error", 4],
+        "linebreak-style": ["error", "unix"],
+        "quotes": ["error", "double"],
+        "semi": ["error", "always"],
+
+        // override configuration set by extending "eslint:recommended"
+        "no-empty": "warn",
+        "no-cond-assign": ["error", "always"],
+
+        // disable rules from base configurations
+         "for-direction": "off",
+    }
+}
+```
+
+### Using a configuration from a plugin
+
+A [plugin](https://eslint.org/docs/developer-guide/working-with-plugins) is an npm package that can add various extensions to ESLint. A plugin can perform numerous functions, including but not limited to adding new rules and exporting [shareable configurations](https://eslint.org/docs/developer-guide/working-with-plugins#configs-in-plugins). Make sure the package has been installed in a directory where ESLint can require it.
+
+The `plugins` [property value](./plugins.md#configuring-plugins) can omit the `eslint-plugin-` prefix of the package name.
+
+The `extends` property value can consist of:
+
+* `plugin:`
+* the package name (from which you can omit the prefix, for example, `react` is short for `eslint-plugin-react`)
+* `/`
+* the configuration name (for example, `recommended`)
+
+Example of a configuration file in JSON format:
+
+```json
+{
+    "plugins": [
+        "react"
+    ],
+    "extends": [
+        "eslint:recommended",
+        "plugin:react/recommended"
+    ],
+    "rules": {
+       "react/no-set-state": "off"
+    }
+}
+```
+
+### Using a configuration file
+
+The `extends` property value can be an absolute or relative path to a base [configuration file](#using-configuration-files). ESLint resolves a relative path to a base configuration file relative to the configuration file that uses it.
+
+Example of a configuration file in JSON format:
+
+```json
+{
+    "extends": [
+        "./node_modules/coding-standard/eslintDefaults.js",
+        "./node_modules/coding-standard/.eslintrc-es6",
+        "./node_modules/coding-standard/.eslintrc-jsx"
+    ],
+    "rules": {
+        "eqeqeq": "warn"
+    }
+}
+```
+
+### Using `"eslint:all"`
+
+The `extends` property value can be `"eslint:all"` to enable all core rules in the currently installed version of ESLint. The set of core rules can change at any minor or major version of ESLint.
+
+**Important:** This configuration is **not recommended for production use** because it changes with every minor and major version of ESLint. Use it at your own risk.
+
+You might enable all core rules as a shortcut to explore rules and options while you decide on the configuration for a project, especially if you rarely override options or disable rules. The default options for rules are not endorsements by ESLint (for example, the default option for the [`quotes`](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.
+
+Example of a configuration file in JavaScript format:
+
+```js
+module.exports = {
+    "extends": "eslint:all",
+    "rules": {
+        // override default options
+        "comma-dangle": ["error", "always"],
+        "indent": ["error", 2],
+        "no-cond-assign": ["error", "always"],
+
+        // disable now, but enable in the future
+        "one-var": "off", // ["error", "never"]
+
+        // disable
+        "init-declarations": "off",
+        "no-console": "off",
+        "no-inline-comments": "off",
+    }
+}
+```
+
+## Configuration Based on Glob Patterns
+
+<b>v4.1.0+.</b> Sometimes a more fine-controlled configuration is necessary, for example, if the configuration for files within the same directory has to be different. Therefore you can provide configurations under the `overrides` key that will only apply to files that match specific glob patterns, using the same format you would pass on the command line (e.g., `app/**/*.test.js`).
+
+### How do overrides work?
+
+It is possible to override settings based on file glob patterns in your configuration by using the `overrides` key. An example of using the `overrides` key is as follows:
+
+In your `.eslintrc.json`:
+
+```json
+{
+  "rules": {
+    "quotes": ["error", "double"]
+  },
+
+  "overrides": [
+    {
+      "files": ["bin/*.js", "lib/*.js"],
+      "excludedFiles": "*.test.js",
+      "rules": {
+        "quotes": ["error", "single"]
+      }
+    }
+  ]
+}
+```
+
+Here is how overrides work in a configuration file:
+
+* The patterns are applied against the file path relative to the directory of the config file. For example, if your config file has the path `/Users/john/workspace/any-project/.eslintrc.js` and the file you want to lint has the path `/Users/john/workspace/any-project/lib/util.js`, then the pattern provided in `.eslintrc.js` will be executed against the relative path `lib/util.js`.
+* Glob pattern overrides have higher precedence than the regular configuration in the same config file. Multiple overrides within the same config are applied in order. That is, the last override block in a config file always has the highest precedence.
+* A glob specific configuration works almost the same as any other ESLint config. Override blocks can contain any configuration options that are valid in a regular config, with the exception of `root` and `ignorePatterns`.
+    * A glob specific configuration can have an `extends` setting, but the `root` property in the extended configs is ignored. The `ignorePatterns` property in the extended configs is used only for the files the glob specific configuration matched.
+    * Nested `overrides` setting will be applied only if the glob patterns of both of the parent config and the child config matched. This is the same when the extended configs have an `overrides` setting.
+* Multiple glob patterns can be provided within a single override block. A file must match at least one of the supplied patterns for the configuration to apply.
+* Override blocks can also specify patterns to exclude from matches. If a file matches any of the excluded patterns, the configuration won't apply.
+
+### Relative glob patterns
+
+```
+project-root
+├── app
+│   ├── lib
+│   │   ├── foo.js
+│   │   ├── fooSpec.js
+│   ├── components
+│   │   ├── bar.js
+│   │   ├── barSpec.js
+│   ├── .eslintrc.json
+├── server
+│   ├── server.js
+│   ├── serverSpec.js
+├── .eslintrc.json
+```
+
+The config in `app/.eslintrc.json` defines the glob pattern `**/*Spec.js`. This pattern is relative to the base directory of `app/.eslintrc.json`. So, this pattern would match `app/lib/fooSpec.js` and `app/components/barSpec.js` but **NOT** `server/serverSpec.js`. If you defined the same pattern in the `.eslintrc.json` file within in the `project-root` folder, it would match all three of the `*Spec` files.
+
+If a config is provided via the `--config` CLI option, the glob patterns in the config are relative to the current working directory rather than the base directory of the given config. For example, if `--config configs/.eslintrc.json` is present, the glob patterns in the config are relative to `.` rather than `./configs`.
+
+### Specifying target files to lint
+
+If you specified directories with CLI (e.g., `eslint lib`), ESLint searches target files in the directory to lint. The target files are `*.js` or the files that match any of `overrides` entries (but exclude entries that are any of `files` end with `*`).
+
+If you specified the [`--ext`](https://eslint.org/docs/user-guide/command-line-interface#ext) command line option along with directories, the target files are only the files that have specified file extensions regardless of `overrides` entries.
+
+## Personal Configuration Files (deprecated)
+
+⚠️ **This feature has been deprecated**. This feature will be removed in the 8.0.0 release. If you want to continue to use personal configuration files, please use the [`--config` CLI option](https://eslint.org/docs/user-guide/command-line-interface#-c---config). For more information regarding this decision, please see [RFC 28](https://github.com/eslint/rfcs/pull/28) and [RFC 32](https://github.com/eslint/rfcs/pull/32).
+
+`~/` refers to [the home directory of the current user on your preferred operating system](https://nodejs.org/api/os.html#os_os_homedir). The personal configuration file being referred to here is `~/.eslintrc.*` file, which is currently handled differently than other configuration files.
+
+### How does ESLint find personal configuration files?
+
+If `eslint` could not find any configuration file in the project, `eslint` loads `~/.eslintrc.*` file.
+
+If `eslint` could find configuration files in the project, `eslint` ignores `~/.eslintrc.*` file even if it's in an ancestor directory of the project directory.
+
+### How do personal configuration files behave?
+
+`~/.eslintrc.*` files behave similarly to regular configuration files, with some exceptions:
+
+`~/.eslintrc.*` files load shareable configs and custom parsers from `~/node_modules/` – similarly to `require()` – in the user's home directory. Please note that it doesn't load global-installed packages.
+
+`~/.eslintrc.*` files load plugins from `$CWD/node_modules` by default in order to identify plugins uniquely. If you want to use plugins with `~/.eslintrc.*` files, plugins must be installed locally per project. Alternatively, you can use the [`--resolve-plugins-relative-to` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--resolve-plugins-relative-to) to change the location from which ESLint loads plugins.
diff --git a/eslint/docs/user-guide/configuring/ignoring-code.md b/eslint/docs/user-guide/configuring/ignoring-code.md
new file mode 100644 (file)
index 0000000..d7b3cfd
--- /dev/null
@@ -0,0 +1,155 @@
+# Ignoring Code
+
+
+* [`ignorePatterns` in Config Files](#ignorepatterns-in-config-files)
+* [The `.eslintignore` File](#the-eslintignore-file)
+* [Using an Alternate File](#using-an-alternate-file)
+* [Using eslintIgnore in package.json](#using-eslintignore-in-packagejson)
+* [Ignored File Warnings](#ignored-file-warnings)
+
+## `ignorePatterns` in Config Files
+
+You can tell ESLint to ignore specific files and directories using `ignorePatterns` in your config files. `ignorePatterns` patterns follow the same rules as `.eslintignore`. Please see the [the `.eslintignore` file documentation](./ignoring-code.md#the-eslintignore-file) to learn more.
+
+```json
+{
+    "ignorePatterns": ["temp.js", "**/vendor/*.js"],
+    "rules": {
+        //...
+    }
+}
+```
+
+* Glob patterns in `ignorePatterns` are relative to the directory that the config file is placed in.
+* You cannot write `ignorePatterns` property under `overrides` property.
+* Patterns defined in `.eslintignore` take precedence over the `ignorePatterns` property of config files.
+
+If a glob pattern starts with `/`, the pattern is relative to the base directory of the config file. For example, `/foo.js` in `lib/.eslintrc.json` matches to `lib/foo.js` but not `lib/subdir/foo.js`.
+
+If a config is provided via the `--config` CLI option, the ignore patterns that start with `/` in the config are relative to the current working directory rather than the base directory of the given config. For example, if `--config configs/.eslintrc.json` is present, the ignore patterns in the config are relative to `.` rather than `./configs`.
+
+## The `.eslintignore` File
+
+You can tell ESLint to ignore specific files and directories by creating an `.eslintignore` file in your project's root directory. The `.eslintignore` file is a plain text file where each line is a glob pattern indicating which paths should be omitted from linting. For example, the following will omit all JavaScript files:
+
+```text
+**/*.js
+```
+
+When ESLint is run, it looks in the current working directory to find an `.eslintignore` file before determining which files to lint. If this file is found, then those preferences are applied when traversing directories. Only one `.eslintignore` file can be used at a time, so `.eslintignore` files other than the one in the current working directory will not be used.
+
+Globs are matched using [node-ignore](https://github.com/kaelzhang/node-ignore), so a number of features are available:
+
+* Lines beginning with `#` are treated as comments and do not affect the ignore patterns.
+* Paths are relative to the current working directory. This is also true of paths passed in via the `--ignore-pattern` [command](https://eslint.org/docs/user-guide/command-line-interface#--ignore-pattern).
+* Lines preceded by `!` are negated patterns that re-include a pattern that was ignored by an earlier pattern.
+* Ignore patterns behave according to the `.gitignore` [specification](https://git-scm.com/docs/gitignore).
+
+Of particular note is that like `.gitignore` files, all paths used as patterns for both `.eslintignore` and `--ignore-pattern` must use forward slashes as their path separators.
+
+```text
+# Valid
+/root/src/*.js
+
+# Invalid
+\root\src\*.js
+```
+
+Please see [`.gitignore`](https://git-scm.com/docs/gitignore)'s specification for further examples of valid syntax.
+
+In addition to any patterns in the `.eslintignore` file, ESLint always follows a couple of implicit ignore rules even if the `--no-ignore` flag is passed. The implicit rules are as follows:
+
+* `node_modules/` is ignored.
+* dot-files (except for `.eslintrc.*`), as well as dot-folders and their contents, are ignored.
+
+There are also some exceptions to these rules:
+
+* If the path to lint is a glob pattern or directory path and contains a dot-folder, all dot-files and dot-folders will be linted. This includes dot-files and dot-folders that are buried deeper in the directory structure.
+
+  For example, `eslint .config/` will lint all dot-folders and dot-files in the `.config` directory, including immediate children as well as children that are deeper in the directory structure.
+
+* If the path to lint is a specific file path and the `--no-ignore` flag has been passed, ESLint will lint the file regardless of the implicit ignore rules.
+
+  For example, `eslint .config/my-config-file.js --no-ignore` will cause `my-config-file.js` to be linted. It should be noted that the same command without the `--no-ignore` line will not lint the `my-config-file.js` file.
+
+* Allowlist and denylist rules specified via `--ignore-pattern` or `.eslintignore` are prioritized above implicit ignore rules.
+
+  For example, in this scenario, `.build/test.js` is the desired file to allowlist. Because all dot-folders and their children are ignored by default, `.build` must first be allowlisted so that eslint becomes aware of its children. Then, `.build/test.js` must be explicitly allowlisted, while the rest of the content is denylisted. This is done with the following `.eslintignore` file:
+
+  ```text
+  # Allowlist 'test.js' in the '.build' folder
+  # But do not allow anything else in the '.build' folder to be linted
+  !.build
+  .build/*
+  !.build/test.js
+  ```
+
+  The following `--ignore-pattern` is also equivalent:
+
+      eslint --ignore-pattern '!.build' --ignore-pattern '.build/*' --ignore-pattern '!.build/test.js' parent-folder/
+
+## Using an Alternate File
+
+If you'd prefer to use a different file than the `.eslintignore` in the current working directory, you can specify it on the command line using the `--ignore-path` option. For example, you can use `.jshintignore` file because it has the same format:
+
+    eslint --ignore-path .jshintignore file.js
+
+You can also use your `.gitignore` file:
+
+    eslint --ignore-path .gitignore file.js
+
+Any file that follows the standard ignore file format can be used. Keep in mind that specifying `--ignore-path` means that any existing `.eslintignore` file will not be used. Note that globbing rules in `.eslintignore` follow those of `.gitignore`.
+
+## Using eslintIgnore in package.json
+
+If an `.eslintignore` file is not found and an alternate file is not specified, ESLint will look in package.json for an `eslintIgnore` key to check for files to ignore.
+
+    {
+      "name": "mypackage",
+      "version": "0.0.1",
+      "eslintConfig": {
+          "env": {
+              "browser": true,
+              "node": true
+          }
+      },
+      "eslintIgnore": ["hello.js", "world.js"]
+    }
+
+## Ignored File Warnings
+
+When you pass directories to ESLint, files and directories are silently ignored. If you pass a specific file to ESLint, then you will see a warning indicating that the file was skipped. For example, suppose you have an `.eslintignore` file that looks like this:
+
+```text
+foo.js
+```
+
+And then you run:
+
+    eslint foo.js
+
+You'll see this warning:
+
+```text
+foo.js
+  0:0  warning  File ignored because of a matching ignore pattern. Use "--no-ignore" to override.
+
+✖ 1 problem (0 errors, 1 warning)
+```
+
+This message occurs because ESLint is unsure if you wanted to actually lint the file or not. As the message indicates, you can use `--no-ignore` to omit using the ignore rules.
+
+Consider another scenario where you may want to run ESLint on a specific dot-file or dot-folder, but have forgotten to specifically allow those files in your `.eslintignore` file. You would run something like this:
+
+    eslint .config/foo.js
+
+You would see this warning:
+
+```text
+.config/foo.js
+  0:0  warning  File ignored by default.  Use a negated ignore pattern (like "--ignore-pattern '!<relative/path/to/filename>'") to override
+
+✖ 1 problem (0 errors, 1 warning)
+```
+
+This message occurs because, normally, this file would be ignored by ESLint's implicit ignore rules (as mentioned above). A negated ignore rule in your `.eslintignore` file would override the implicit rule and reinclude this file for linting. Additionally, in this specific case, `--no-ignore` could be used to lint the file as well.
diff --git a/eslint/docs/user-guide/configuring/language-options.md b/eslint/docs/user-guide/configuring/language-options.md
new file mode 100644 (file)
index 0000000..eb3fe8a
--- /dev/null
@@ -0,0 +1,214 @@
+# Language Options
+
+
+* [Specifying Environments](#specifying-environments)
+* [Specifying Globals](#specifying-globals)
+* [Specifying Parser Options](#specifying-parser-options)
+
+## Specifying Environments
+
+An environment provides predefined global variables. The available environments are:
+
+* `browser` - browser global variables.
+* `node` - Node.js global variables and Node.js scoping.
+* `commonjs` - CommonJS global variables and CommonJS scoping (use this for browser-only code that uses Browserify/WebPack).
+* `shared-node-browser` - Globals common to both Node.js and Browser.
+* `es6` - enable all ECMAScript 6 features except for modules (this automatically sets the `ecmaVersion` parser option to 6).
+* `es2017` - adds all ECMAScript 2017 globals and automatically sets the `ecmaVersion` parser option to 8.
+* `es2020` - adds all ECMAScript 2020 globals and automatically sets the `ecmaVersion` parser option to 11.
+* `es2021` - adds all ECMAScript 2021 globals and automatically sets the `ecmaVersion` parser option to 12.
+* `worker` - web workers global variables.
+* `amd` - defines `require()` and `define()` as global variables as per the [amd](https://github.com/amdjs/amdjs-api/wiki/AMD) spec.
+* `mocha` - adds all of the Mocha testing global variables.
+* `jasmine` - adds all of the Jasmine testing global variables for version 1.3 and 2.0.
+* `jest` - Jest global variables.
+* `phantomjs` - PhantomJS global variables.
+* `protractor` - Protractor global variables.
+* `qunit` - QUnit global variables.
+* `jquery` - jQuery global variables.
+* `prototypejs` - Prototype.js global variables.
+* `shelljs` - ShellJS global variables.
+* `meteor` - Meteor global variables.
+* `mongo` - MongoDB global variables.
+* `applescript` - AppleScript global variables.
+* `nashorn` - Java 8 Nashorn global variables.
+* `serviceworker` - Service Worker global variables.
+* `atomtest` - Atom test helper globals.
+* `embertest` - Ember test helper globals.
+* `webextensions` - WebExtensions globals.
+* `greasemonkey` - GreaseMonkey globals.
+
+These environments are not mutually exclusive, so you can define more than one at a time.
+
+Environments can be specified inside of a file, in configuration files or using the `--env` [command line](https://eslint.org/docs/user-guide/command-line-interface) flag.
+
+### Using configuration comments
+
+To specify environments using a comment inside of your JavaScript file, use the following format:
+
+```js
+/* eslint-env node, mocha */
+```
+
+This enables Node.js and Mocha environments.
+
+### Using configuration files
+
+To specify environments in a configuration file, use the `env` key and specify which environments you want to enable by setting each to `true`. For example, the following enables the browser and Node.js environments:
+
+```json
+{
+    "env": {
+        "browser": true,
+        "node": true
+    }
+}
+```
+
+Or in a `package.json` file
+
+```json
+{
+    "name": "mypackage",
+    "version": "0.0.1",
+    "eslintConfig": {
+        "env": {
+            "browser": true,
+            "node": true
+        }
+    }
+}
+```
+
+And in YAML:
+
+```yaml
+---
+  env:
+    browser: true
+    node: true
+```
+
+### Using a plugin
+
+If you want to use an environment from a plugin, be sure to specify the plugin name in the `plugins` array and then use the unprefixed plugin name, followed by a slash, followed by the environment name. For example:
+
+```json
+{
+    "plugins": ["example"],
+    "env": {
+        "example/custom": true
+    }
+}
+```
+
+Or in a `package.json` file
+
+```json
+{
+    "name": "mypackage",
+    "version": "0.0.1",
+    "eslintConfig": {
+        "plugins": ["example"],
+        "env": {
+            "example/custom": true
+        }
+    }
+}
+```
+
+## Specifying Globals
+
+Some of ESLint's core rules rely on knowledge of the global variables available to your code at runtime. Since these can vary greatly between different environments as well as be modified at runtime, ESLint makes no assumptions about what global variables exist in your execution environment. If you would like to use rules that require knowledge of what global variables are available, you can define global variables in your configuration file or by using configuration comments in your source code.
+
+### Using configuration comments
+
+To specify globals using a comment inside of your JavaScript file, use the following format:
+
+```js
+/* global var1, var2 */
+```
+
+This defines two global variables, `var1` and `var2`. If you want to optionally specify that these global variables can be written to (rather than only being read), then you can set each with a `"writable"` flag:
+
+```js
+/* global var1:writable, var2:writable */
+```
+
+### Using configuration files
+
+To configure global variables inside of a configuration file, set the `globals` configuration property to an object containing keys named for each of the global variables you want to use. For each global variable key, set the corresponding value equal to `"writable"` to allow the variable to be overwritten or `"readonly"` to disallow overwriting. For example:
+
+```json
+{
+    "globals": {
+        "var1": "writable",
+        "var2": "readonly"
+    }
+}
+```
+
+And in YAML:
+
+```yaml
+---
+  globals:
+    var1: writable
+    var2: readonly
+```
+
+These examples allow `var1` to be overwritten in your code, but disallow it for `var2`.
+
+Globals can be disabled with the string `"off"`. For example, in an environment where most ES2015 globals are available but `Promise` is unavailable, you might use this config:
+
+```json
+{
+    "env": {
+        "es6": true
+    },
+    "globals": {
+        "Promise": "off"
+    }
+}
+```
+
+For historical reasons, the boolean value `false` and the string value `"readable"` are equivalent to `"readonly"`. Similarly, the boolean value `true` and the string value `"writeable"` are equivalent to `"writable"`. However, the use of older values is deprecated.
+
+
+## Specifying Parser Options
+
+ESLint allows you to specify the JavaScript language options you want to support. By default, ESLint expects ECMAScript 5 syntax. You can override that setting to enable support for other ECMAScript versions as well as JSX by using parser options.
+
+Please note that supporting JSX syntax is not the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://github.com/yannickcr/eslint-plugin-react) if you are using React and want React semantics.
+By the same token, supporting ES6 syntax is not the same as supporting new ES6 globals (e.g., new types such as
+`Set`).
+For ES6 syntax, use `{ "parserOptions": { "ecmaVersion": 6 } }`; for new ES6 global variables, use `{ "env":
+{ "es6": true } }`. `{ "env": { "es6": true } }` enables ES6 syntax automatically, but `{ "parserOptions": { "ecmaVersion": 6 } }` does not enable ES6 globals automatically.
+
+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.
+* `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
+    * `impliedStrict` - enable global [strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode) (if `ecmaVersion` is 5 or greater)
+    * `jsx` - enable [JSX](https://facebook.github.io/jsx/)
+
+Here's an example `.eslintrc.json` file:
+
+```json
+{
+    "parserOptions": {
+        "ecmaVersion": 6,
+        "sourceType": "module",
+        "ecmaFeatures": {
+            "jsx": true
+        }
+    },
+    "rules": {
+        "semi": "error"
+    }
+}
+```
+
+Setting parser options helps ESLint determine what is a parsing error. All language options are `false` by default.
diff --git a/eslint/docs/user-guide/configuring/plugins.md b/eslint/docs/user-guide/configuring/plugins.md
new file mode 100644 (file)
index 0000000..0235586
--- /dev/null
@@ -0,0 +1,177 @@
+# Plugins
+
+
+* [Specifying Parser](#specifying-parser)
+* [Specifying Processor](#specifying-processor)
+* [Configuring Plugins](#configuring-plugins)
+
+## Specifying Parser
+
+By default, ESLint uses [Espree](https://github.com/eslint/espree) as its parser. You can optionally specify that a different parser should be used in your configuration file so long as the parser meets the following requirements:
+
+1. It must be a Node module loadable from the config file where the parser is used. Usually, this means you should install the parser package separately using npm.
+1. It must conform to the [parser interface](https://eslint.org/docs/developer-guide/working-with-custom-parsers).
+
+Note that even with these compatibilities, there are no guarantees that an external parser will work correctly with ESLint and ESLint will not fix bugs related to incompatibilities with other parsers.
+
+To indicate the npm module to use as your parser, specify it using the `parser` option in your `.eslintrc` file. For example, the following specifies to use Esprima instead of Espree:
+
+```json
+{
+    "parser": "esprima",
+    "rules": {
+        "semi": "error"
+    }
+}
+```
+
+The following parsers are compatible with ESLint:
+
+* [Esprima](https://www.npmjs.com/package/esprima)
+* [@babel/eslint-parser](https://www.npmjs.com/package/@babel/eslint-parser) - A wrapper around the [Babel](https://babeljs.io) parser that makes it compatible with ESLint.
+* [@typescript-eslint/parser](https://www.npmjs.com/package/@typescript-eslint/parser) - A parser that converts TypeScript into an ESTree-compatible form so it can be used in ESLint.
+
+Note when using a custom parser, the `parserOptions` configuration property is still required for ESLint to work properly with features not in ECMAScript 5 by default. Parsers are all passed `parserOptions` and may or may not use them to determine which features to enable.
+
+## Specifying Processor
+
+Plugins may provide processors. Processors can extract JavaScript code from other kinds of files, then let ESLint lint the JavaScript code or processors can convert JavaScript code in preprocessing for some purpose.
+
+To specify processors in a configuration file, use the `processor` key with the concatenated string of a plugin name and a processor name by a slash. For example, the following enables the processor `a-processor` that the plugin `a-plugin` provided:
+
+```json
+{
+    "plugins": ["a-plugin"],
+    "processor": "a-plugin/a-processor"
+}
+```
+
+To specify processors for specific kinds of files, use the combination of the `overrides` key and the `processor` key. For example, the following uses the processor `a-plugin/markdown` for `*.md` files.
+
+```json
+{
+    "plugins": ["a-plugin"],
+    "overrides": [
+        {
+            "files": ["*.md"],
+            "processor": "a-plugin/markdown"
+        }
+    ]
+}
+```
+
+Processors may make named code blocks such as `0.js` and `1.js`. ESLint handles such a named code block as a child file of the original file. You can specify additional configurations for named code blocks in the `overrides` section of the config. For example, the following disables the `strict` rule for the named code blocks which end with `.js` in markdown files.
+
+```json
+{
+    "plugins": ["a-plugin"],
+    "overrides": [
+        {
+            "files": ["*.md"],
+            "processor": "a-plugin/markdown"
+        },
+        {
+            "files": ["**/*.md/*.js"],
+            "rules": {
+                "strict": "off"
+            }
+        }
+    ]
+}
+```
+
+ESLint checks the file path of named code blocks then ignores those if any `overrides` entry didn't match the file path. Be sure to add an `overrides` entry if you want to lint named code blocks other than `*.js`.
+
+## Configuring Plugins
+
+ESLint supports the use of third-party plugins. Before using the plugin, you have to install it using npm.
+
+To configure plugins inside of a configuration file, use the `plugins` key, which contains a list of plugin names. The `eslint-plugin-` prefix can be omitted from the plugin name.
+
+```json
+{
+    "plugins": [
+        "plugin1",
+        "eslint-plugin-plugin2"
+    ]
+}
+```
+
+And in YAML:
+
+```yaml
+---
+  plugins:
+    - plugin1
+    - eslint-plugin-plugin2
+```
+
+**Notes:**
+
+1. Plugins are resolved relative to the config file. In other words, ESLint will load the plugin as a user would obtain by running `require('eslint-plugin-pluginname')` in the config file.
+2. Plugins in the base configuration (loaded by `extends` setting) are relative to the derived config file. For example, if `./.eslintrc` has `extends: ["foo"]` and the `eslint-config-foo` has `plugins: ["bar"]`, ESLint finds the `eslint-plugin-bar` from `./node_modules/` (rather than `./node_modules/eslint-config-foo/node_modules/`) or ancestor directories. Thus every plugin in the config file and base configurations is resolved uniquely.
+
+### Naming convention
+
+#### Include a plugin
+
+The `eslint-plugin-` prefix can be omitted for non-scoped packages
+
+```js
+{
+    // ...
+    "plugins": [
+        "jquery", // means eslint-plugin-jquery
+    ]
+    // ...
+}
+```
+
+The same rule does apply to scoped packages:
+
+```js
+{
+    // ...
+    "plugins": [
+        "@jquery/jquery", // means @jquery/eslint-plugin-jquery
+        "@foobar" // means @foobar/eslint-plugin
+    ]
+    // ...
+}
+```
+
+#### Use a plugin
+
+When using rules, environments or configs defined by plugins, they must be referenced following the convention:
+
+* `eslint-plugin-foo` → `foo/a-rule`
+* `@foo/eslint-plugin` → `@foo/a-config`
+* `@foo/eslint-plugin-bar` → `@foo/bar/a-environment`
+
+For example:
+
+```js
+{
+    // ...
+    "plugins": [
+        "jquery",   // eslint-plugin-jquery
+        "@foo/foo", // @foo/eslint-plugin-foo
+        "@bar"      // @bar/eslint-plugin
+    ],
+    "extends": [
+        "plugin:@foo/foo/recommended",
+        "plugin:@bar/recommended"
+    ],
+    "rules": {
+        "jquery/a-rule": "error",
+        "@foo/foo/some-rule": "error",
+        "@bar/another-rule": "error"
+    },
+    "env": {
+        "jquery/jquery": true,
+        "@foo/foo/env-foo": true,
+        "@bar/env-bar": true,
+    }
+    // ...
+}
+```
diff --git a/eslint/docs/user-guide/configuring/rules.md b/eslint/docs/user-guide/configuring/rules.md
new file mode 100644 (file)
index 0000000..2274b98
--- /dev/null
@@ -0,0 +1,264 @@
+# Rules
+
+
+* [Configuring Rules](#configuring-rules)
+* [Disabling Rules](#disabling-rules)
+
+## Configuring Rules
+
+ESLint comes with a large number of built-in rules and you can add more rules through plugins. You can modify which rules your project uses either using configuration comments or configuration files. To change a rule setting, you must set the rule ID equal to one of these values:
+
+* `"off"` or `0` - turn the rule off
+* `"warn"` or `1` - turn the rule on as a warning (doesn't affect exit code)
+* `"error"` or `2` - turn the rule on as an error (exit code is 1 when triggered)
+
+### Using configuration comments
+
+To configure rules inside of a file using configuration comments, use a comment in the following format:
+
+```js
+/* eslint eqeqeq: "off", curly: "error" */
+```
+
+In this example, [`eqeqeq`](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 */
+```
+
+This example is the same as the last example, only it uses the numeric codes instead of the string values. The `eqeqeq` rule is off and the `curly` rule is set to be an error.
+
+If a rule has additional options, you can specify them using array literal syntax, such as:
+
+```js
+/* eslint quotes: ["error", "double"], curly: 2 */
+```
+
+This comment specifies the "double" option for the [`quotes`](https://eslint.org/docs/rules/quotes) rule. The first item in the array is always the rule severity (number or string).
+
+Configuration comments can include descriptions to explain why the comment is necessary. The description must occur after the configuration and is separated from the configuration by two or more consecutive `-` characters. For example:
+
+```js
+/* eslint eqeqeq: "off", curly: "error" -- Here's a description about why this configuration is necessary. */
+```
+
+```js
+/* eslint eqeqeq: "off", curly: "error"
+    --------
+    Here's a description about why this configuration is necessary. */
+```
+
+```js
+/* eslint eqeqeq: "off", curly: "error"
+ * --------
+ * This will not work due to the line above starting with a '*' character.
+ */
+```
+
+### Using configuration files
+
+To configure rules inside of a configuration file, use the `rules` key along with an error level and any options you want to use. For example:
+
+```json
+{
+    "rules": {
+        "eqeqeq": "off",
+        "curly": "error",
+        "quotes": ["error", "double"]
+    }
+}
+```
+
+And in YAML:
+
+```yaml
+---
+rules:
+  eqeqeq: off
+  curly: error
+  quotes:
+    - error
+    - double
+```
+
+To configure a rule which is defined within a plugin you have to prefix the rule ID with the plugin name and a `/`. For example:
+
+```json
+{
+    "plugins": [
+        "plugin1"
+    ],
+    "rules": {
+        "eqeqeq": "off",
+        "curly": "error",
+        "quotes": ["error", "double"],
+        "plugin1/rule1": "error"
+    }
+}
+```
+
+And in YAML:
+
+```yaml
+---
+plugins:
+  - plugin1
+rules:
+  eqeqeq: 0
+  curly: error
+  quotes:
+    - error
+    - "double"
+  plugin1/rule1: error
+```
+
+In these configuration files, the rule `plugin1/rule1` comes from the plugin named `plugin1`. You can also use this format with configuration comments, such as:
+
+```js
+/* eslint "plugin1/rule1": "error" */
+```
+
+**Note:** When specifying rules from plugins, make sure to omit `eslint-plugin-`. ESLint uses only the unprefixed name internally to locate rules.
+
+## Disabling Rules
+
+### Using configuration comments
+
+To temporarily disable rule warnings in your file, use block comments in the following format:
+
+```js
+/* eslint-disable */
+
+alert('foo');
+
+/* eslint-enable */
+```
+
+You can also disable or enable warnings for specific rules:
+
+```js
+/* eslint-disable no-alert, no-console */
+
+alert('foo');
+console.log('bar');
+
+/* eslint-enable no-alert, no-console */
+```
+
+To disable rule warnings in an entire file, put a `/* eslint-disable */` block comment at the top of the file:
+
+```js
+/* eslint-disable */
+
+alert('foo');
+```
+
+You can also disable or enable specific rules for an entire file:
+
+```js
+/* eslint-disable no-alert */
+
+alert('foo');
+```
+
+To disable all rules on a specific line, use a line or block comment in one of the following formats:
+
+```js
+alert('foo'); // eslint-disable-line
+
+// eslint-disable-next-line
+alert('foo');
+
+/* eslint-disable-next-line */
+alert('foo');
+
+alert('foo'); /* eslint-disable-line */
+```
+
+To disable a specific rule on a specific line:
+
+```js
+alert('foo'); // eslint-disable-line no-alert
+
+// eslint-disable-next-line no-alert
+alert('foo');
+
+alert('foo'); /* eslint-disable-line no-alert */
+
+/* eslint-disable-next-line no-alert */
+alert('foo');
+```
+
+To disable multiple rules on a specific line:
+
+```js
+alert('foo'); // eslint-disable-line no-alert, quotes, semi
+
+// eslint-disable-next-line no-alert, quotes, semi
+alert('foo');
+
+alert('foo'); /* eslint-disable-line no-alert, quotes, semi */
+
+/* eslint-disable-next-line no-alert, quotes, semi */
+alert('foo');
+```
+
+All of the above methods also work for plugin rules. For example, to disable `eslint-plugin-example`'s `rule-name` rule, combine the plugin's name (`example`) and the rule's name (`rule-name`) into `example/rule-name`:
+
+```js
+foo(); // eslint-disable-line example/rule-name
+foo(); /* eslint-disable-line example/rule-name */
+```
+
+Configuration comments can include descriptions to explain why the comment is necessary. The description must come after the configuration and needs to be separated from the configuration by two or more consecutive `-` characters. For example:
+
+```js
+// eslint-disable-next-line no-console -- Here's a description about why this configuration is necessary.
+console.log('hello');
+```
+
+**Note:** Comments that disable warnings for a portion of a file tell ESLint not to report rule violations for the disabled code. ESLint still parses the entire file, however, so disabled code still needs to be syntactically valid JavaScript.
+
+### Using configuration files
+
+To disable rules inside of a configuration file for a group of files, use the `overrides` key along with a `files` key. For example:
+
+```json
+{
+  "rules": {...},
+  "overrides": [
+    {
+      "files": ["*-test.js","*.spec.js"],
+      "rules": {
+        "no-unused-expressions": "off"
+      }
+    }
+  ]
+}
+```
+
+### Disabling Inline Comments
+
+To disable all inline config comments, use the `noInlineConfig` setting. For example:
+
+```json
+{
+  "rules": {...},
+  "noInlineConfig": true
+}
+```
+
+This setting is similar to [--no-inline-config](https://eslint.org/docs/user-guide/command-line-interface#--no-inline-config) CLI option.
+
+#### Report unused `eslint-disable` comments
+
+To report unused `eslint-disable` comments, use the `reportUnusedDisableDirectives` setting. For example:
+
+```json
+{
+  "rules": {...},
+  "reportUnusedDisableDirectives": true
+}
+```
+
+This setting is similar to [--report-unused-disable-directives](https://eslint.org/docs/user-guide/command-line-interface#--report-unused-disable-directives) CLI option, but doesn't fail linting (reports as `"warn"` severity).
index 93fef31e061120c17321e329d40f4a8134f08a4f..3b28a91a2f6e5af31123851e4773bfe781fbc20a 100644 (file)
@@ -65,7 +65,7 @@ The names `"semi"` and `"quotes"` are the names of [rules](/docs/rules) in ESLin
 * `"warn"` or `1` - turn the rule on as a warning (doesn't affect exit code)
 * `"error"` or `2` - turn the rule on as an error (exit code will be 1)
 
-The three error levels allow you fine-grained control over how ESLint applies rules (for more configuration options and details, see the [configuration docs](configuring.md)).
+The three error levels allow you fine-grained control over how ESLint applies rules (for more configuration options and details, see the [configuration docs](configuring/)).
 
 Your `.eslintrc.{js,yml,json}` configuration file will also include the line:
 
@@ -81,7 +81,7 @@ Because of this line, all of the rules marked "(recommended)" on the [rules page
 
 ## Next Steps
 
-* Learn about [advanced configuration](configuring.md) of ESLint.
+* Learn about [advanced configuration](configuring/) of ESLint.
 * Get familiar with the [command line options](command-line-interface.md).
 * Explore [ESLint integrations](integrations.md) into other tools like editors, build systems, and more.
 * Can't find just the right rule?  Make your own [custom rule](/docs/developer-guide/working-with-rules.md).
index 974c8972136b97b85589168da85190e202fd127a..05f1a1ceb6eea550115e56439d916d028c0a7391 100644 (file)
@@ -1,5 +1,6 @@
 "use strict";
 const os = require("os");
+const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
 
 if (os.arch() === "arm64") {
 
@@ -20,12 +21,11 @@ module.exports = function(config) {
          * frameworks to use
          * available frameworks: https://npmjs.org/browse/keyword/karma-adapter
          */
-        frameworks: ["mocha"],
+        frameworks: ["mocha", "webpack"],
 
 
         // list of files / patterns to load in the browser
         files: [
-            "build/eslint.js",
             "tests/lib/linter/linter.js"
         ],
 
@@ -44,6 +44,14 @@ module.exports = function(config) {
         },
         webpack: {
             mode: "none",
+            plugins: [
+                new NodePolyfillPlugin()
+            ],
+            resolve: {
+                alias: {
+                    "../../../lib/linter$": "../../../build/eslint.js"
+                }
+            },
             stats: "errors-only"
         },
         webpackMiddleware: {
index 9a414061501e2d1209bc8d19cb24336892eaa923..ca298f9c356c3cb84af2498222faeaca13d7fd35 100644 (file)
@@ -27,16 +27,11 @@ const {
         naming,
         CascadingConfigArrayFactory,
         IgnorePattern,
-        getUsedExtractedConfigs
+        getUsedExtractedConfigs,
+        ModuleResolver
     }
 } = require("@eslint/eslintrc");
 
-/*
- * For some reason, ModuleResolver must be included via filepath instead of by
- * API exports in order to work properly. That's why this is separated out onto
- * its own require() statement.
- */
-const ModuleResolver = require("@eslint/eslintrc/lib/shared/relative-module-resolver");
 const { FileEnumerator } = require("./file-enumerator");
 
 const { Linter } = require("../linter");
@@ -531,7 +526,7 @@ function directoryExists(resolvedPath) {
     try {
         return fs.statSync(resolvedPath).isDirectory();
     } catch (error) {
-        if (error && error.code === "ENOENT") {
+        if (error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
             return false;
         }
         throw error;
@@ -589,7 +584,7 @@ class CLIEngine {
             ignore: options.ignore
         });
         const lintResultCache =
-            options.cache ? new LintResultCache(cacheFilePath) : null;
+            options.cache ? new LintResultCache(cacheFilePath, options.cacheStrategy) : null;
         const linter = new Linter({ cwd: options.cwd });
 
         /** @type {ConfigArray[]} */
index c2961d71ac06327cf283aa5e7e9555d9357f2add..ade28517b425ee6a0d23ce9455a34040b0f424e5 100644 (file)
@@ -38,7 +38,7 @@ const fs = require("fs");
 const path = require("path");
 const getGlobParent = require("glob-parent");
 const isGlob = require("is-glob");
-const { escapeRegExp } = require("lodash");
+const escapeRegExp = require("escape-string-regexp");
 const { Minimatch } = require("minimatch");
 
 const {
@@ -433,9 +433,14 @@ class FileEnumerator {
         // Enumerate the files of this directory.
         for (const entry of readdirSafeSync(directoryPath)) {
             const filePath = path.join(directoryPath, entry.name);
+            const fileInfo = entry.isSymbolicLink() ? statSafeSync(filePath) : entry;
+
+            if (!fileInfo) {
+                continue;
+            }
 
             // Check if the file is matched.
-            if (entry.isFile()) {
+            if (fileInfo.isFile()) {
                 if (!config) {
                     config = configArrayFactory.getConfigArrayForFile(
                         filePath,
@@ -471,7 +476,7 @@ class FileEnumerator {
                 }
 
             // Dive into the sub directory.
-            } else if (options.recursive && entry.isDirectory()) {
+            } else if (options.recursive && fileInfo.isDirectory()) {
                 if (!config) {
                     config = configArrayFactory.getConfigArrayForFile(
                         filePath,
diff --git a/eslint/lib/cli-engine/formatters/html-template-message.html b/eslint/lib/cli-engine/formatters/html-template-message.html
deleted file mode 100644 (file)
index 93795a1..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<tr style="display:none" class="f-<%= parentIndex %>">
-    <td><%= lineNumber %>:<%= columnNumber %></td>
-    <td class="clr-<%= severityNumber %>"><%= severityName %></td>
-    <td><%- message %></td>
-    <td>
-        <a href="<%= ruleUrl %>" target="_blank" rel="noopener noreferrer"><%= ruleId %></a>
-    </td>
-</tr>
diff --git a/eslint/lib/cli-engine/formatters/html-template-page.html b/eslint/lib/cli-engine/formatters/html-template-page.html
deleted file mode 100644 (file)
index 4016576..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-<!DOCTYPE html>
-<html>
-    <head>
-        <meta charset="UTF-8">
-        <title>ESLint Report</title>
-        <style>
-            body {
-                font-family:Arial, "Helvetica Neue", Helvetica, sans-serif;
-                font-size:16px;
-                font-weight:normal;
-                margin:0;
-                padding:0;
-                color:#333
-            }
-            #overview {
-                padding:20px 30px
-            }
-            td, th {
-                padding:5px 10px
-            }
-            h1 {
-                margin:0
-            }
-            table {
-                margin:30px;
-                width:calc(100% - 60px);
-                max-width:1000px;
-                border-radius:5px;
-                border:1px solid #ddd;
-                border-spacing:0px;
-            }
-            th {
-                font-weight:400;
-                font-size:medium;
-                text-align:left;
-                cursor:pointer
-            }
-            td.clr-1, td.clr-2, th span {
-                font-weight:700
-            }
-            th span {
-                float:right;
-                margin-left:20px
-            }
-            th span:after {
-                content:"";
-                clear:both;
-                display:block
-            }
-            tr:last-child td {
-                border-bottom:none
-            }
-            tr td:first-child, tr td:last-child {
-                color:#9da0a4
-            }
-            #overview.bg-0, tr.bg-0 th {
-                color:#468847;
-                background:#dff0d8;
-                border-bottom:1px solid #d6e9c6
-            }
-            #overview.bg-1, tr.bg-1 th {
-                color:#f0ad4e;
-                background:#fcf8e3;
-                border-bottom:1px solid #fbeed5
-            }
-            #overview.bg-2, tr.bg-2 th {
-                color:#b94a48;
-                background:#f2dede;
-                border-bottom:1px solid #eed3d7
-            }
-            td {
-                border-bottom:1px solid #ddd
-            }
-            td.clr-1 {
-                color:#f0ad4e
-            }
-            td.clr-2 {
-                color:#b94a48
-            }
-            td a {
-                color:#3a33d1;
-                text-decoration:none
-            }
-            td a:hover {
-                color:#272296;
-                text-decoration:underline
-            }
-        </style>
-    </head>
-    <body>
-        <div id="overview" class="bg-<%= reportColor %>">
-            <h1>ESLint Report</h1>
-            <div>
-                <span><%= reportSummary %></span> - Generated on <%= date %>
-            </div>
-        </div>
-        <table>
-            <tbody>
-                <%= results %>
-            </tbody>
-        </table>
-        <script type="text/javascript">
-            var groups = document.querySelectorAll("tr[data-group]");
-            for (i = 0; i < groups.length; i++) {
-                groups[i].addEventListener("click", function() {
-                    var inGroup = document.getElementsByClassName(this.getAttribute("data-group"));
-                    this.innerHTML = (this.innerHTML.indexOf("+") > -1) ? this.innerHTML.replace("+", "-") : this.innerHTML.replace("-", "+");
-                    for (var j = 0; j < inGroup.length; j++) {
-                        inGroup[j].style.display = (inGroup[j].style.display !== "none") ? "none" : "table-row";
-                    }
-                });
-            }
-        </script>
-    </body>
-</html>
diff --git a/eslint/lib/cli-engine/formatters/html-template-result.html b/eslint/lib/cli-engine/formatters/html-template-result.html
deleted file mode 100644 (file)
index f4a5593..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-<tr class="bg-<%- color %>" data-group="f-<%- index %>">
-    <th colspan="4">
-        [+] <%- filePath %>
-        <span><%- summary %></span>
-    </th>
-</tr>
index 69f7395550f2e2678120a958f5784c9db4e909e0..baddb63079de4503e6745b466fe8a80e0852eaa4 100644 (file)
  */
 "use strict";
 
-const lodash = require("lodash");
-const fs = require("fs");
-const path = require("path");
-
 //------------------------------------------------------------------------------
 // Helpers
 //------------------------------------------------------------------------------
 
-const pageTemplate = lodash.template(fs.readFileSync(path.join(__dirname, "html-template-page.html"), "utf-8"));
-const messageTemplate = lodash.template(fs.readFileSync(path.join(__dirname, "html-template-message.html"), "utf-8"));
-const resultTemplate = lodash.template(fs.readFileSync(path.join(__dirname, "html-template-result.html"), "utf-8"));
+const encodeHTML = (function() {
+    const encodeHTMLRules = {
+        "&": "&#38;",
+        "<": "&#60;",
+        ">": "&#62;",
+        '"': "&#34;",
+        "'": "&#39;"
+    };
+    const matchHTML = /[&<>"']/ug;
+
+    return function(code) {
+        return code
+            ? code.toString().replace(matchHTML, m => encodeHTMLRules[m] || m)
+            : "";
+    };
+}());
+
+/**
+ * Get the final HTML document.
+ * @param {Object} it data for the document.
+ * @returns {string} HTML document.
+ */
+function pageTemplate(it) {
+    const { reportColor, reportSummary, date, results } = it;
+
+    return `
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="UTF-8">
+        <title>ESLint Report</title>
+        <style>
+            body {
+                font-family:Arial, "Helvetica Neue", Helvetica, sans-serif;
+                font-size:16px;
+                font-weight:normal;
+                margin:0;
+                padding:0;
+                color:#333
+            }
+            #overview {
+                padding:20px 30px
+            }
+            td, th {
+                padding:5px 10px
+            }
+            h1 {
+                margin:0
+            }
+            table {
+                margin:30px;
+                width:calc(100% - 60px);
+                max-width:1000px;
+                border-radius:5px;
+                border:1px solid #ddd;
+                border-spacing:0px;
+            }
+            th {
+                font-weight:400;
+                font-size:medium;
+                text-align:left;
+                cursor:pointer
+            }
+            td.clr-1, td.clr-2, th span {
+                font-weight:700
+            }
+            th span {
+                float:right;
+                margin-left:20px
+            }
+            th span:after {
+                content:"";
+                clear:both;
+                display:block
+            }
+            tr:last-child td {
+                border-bottom:none
+            }
+            tr td:first-child, tr td:last-child {
+                color:#9da0a4
+            }
+            #overview.bg-0, tr.bg-0 th {
+                color:#468847;
+                background:#dff0d8;
+                border-bottom:1px solid #d6e9c6
+            }
+            #overview.bg-1, tr.bg-1 th {
+                color:#f0ad4e;
+                background:#fcf8e3;
+                border-bottom:1px solid #fbeed5
+            }
+            #overview.bg-2, tr.bg-2 th {
+                color:#b94a48;
+                background:#f2dede;
+                border-bottom:1px solid #eed3d7
+            }
+            td {
+                border-bottom:1px solid #ddd
+            }
+            td.clr-1 {
+                color:#f0ad4e
+            }
+            td.clr-2 {
+                color:#b94a48
+            }
+            td a {
+                color:#3a33d1;
+                text-decoration:none
+            }
+            td a:hover {
+                color:#272296;
+                text-decoration:underline
+            }
+        </style>
+    </head>
+    <body>
+        <div id="overview" class="bg-${reportColor}">
+            <h1>ESLint Report</h1>
+            <div>
+                <span>${reportSummary}</span> - Generated on ${date}
+            </div>
+        </div>
+        <table>
+            <tbody>
+                ${results}
+            </tbody>
+        </table>
+        <script type="text/javascript">
+            var groups = document.querySelectorAll("tr[data-group]");
+            for (i = 0; i < groups.length; i++) {
+                groups[i].addEventListener("click", function() {
+                    var inGroup = document.getElementsByClassName(this.getAttribute("data-group"));
+                    this.innerHTML = (this.innerHTML.indexOf("+") > -1) ? this.innerHTML.replace("+", "-") : this.innerHTML.replace("-", "+");
+                    for (var j = 0; j < inGroup.length; j++) {
+                        inGroup[j].style.display = (inGroup[j].style.display !== "none") ? "none" : "table-row";
+                    }
+                });
+            }
+        </script>
+    </body>
+</html>
+`.trimLeft();
+}
 
 /**
  * Given a word and a count, append an s if count is not one.
@@ -58,6 +194,35 @@ function renderColor(totalErrors, totalWarnings) {
     return 0;
 }
 
+/**
+ * Get HTML (table row) describing a single message.
+ * @param {Object} it data for the message.
+ * @returns {string} HTML (table row) describing the message.
+ */
+function messageTemplate(it) {
+    const {
+        parentIndex,
+        lineNumber,
+        columnNumber,
+        severityNumber,
+        severityName,
+        message,
+        ruleUrl,
+        ruleId
+    } = it;
+
+    return `
+<tr style="display:none" class="f-${parentIndex}">
+    <td>${lineNumber}:${columnNumber}</td>
+    <td class="clr-${severityNumber}">${severityName}</td>
+    <td>${encodeHTML(message)}</td>
+    <td>
+        <a href="${ruleUrl ? ruleUrl : ""}" target="_blank" rel="noopener noreferrer">${ruleId ? ruleId : ""}</a>
+    </td>
+</tr>
+`.trimLeft();
+}
+
 /**
  * Get HTML (table rows) describing the messages.
  * @param {Array} messages Messages.
@@ -72,7 +237,7 @@ function renderMessages(messages, parentIndex, rulesMeta) {
      * @param {Object} message Message.
      * @returns {string} HTML (table row) describing a message.
      */
-    return lodash.map(messages, message => {
+    return messages.map(message => {
         const lineNumber = message.line || 0;
         const columnNumber = message.column || 0;
         let ruleUrl;
@@ -80,7 +245,9 @@ function renderMessages(messages, parentIndex, rulesMeta) {
         if (rulesMeta) {
             const meta = rulesMeta[message.ruleId];
 
-            ruleUrl = lodash.get(meta, "docs.url", null);
+            if (meta && meta.docs && meta.docs.url) {
+                ruleUrl = meta.docs.url;
+            }
         }
 
         return messageTemplate({
@@ -96,6 +263,24 @@ function renderMessages(messages, parentIndex, rulesMeta) {
     }).join("\n");
 }
 
+/**
+ * Get HTML (table row) describing the result for a single file.
+ * @param {Object} it data for the file.
+ * @returns {string} HTML (table row) describing the result for the file.
+ */
+function resultTemplate(it) {
+    const { color, index, filePath, summary } = it;
+
+    return `
+<tr class="bg-${color}" data-group="f-${index}">
+    <th colspan="4">
+        [+] ${encodeHTML(filePath)}
+        <span>${encodeHTML(summary)}</span>
+    </th>
+</tr>
+`.trimLeft();
+}
+
 // eslint-disable-next-line jsdoc/require-description
 /**
  * @param {Array} results Test results.
@@ -103,12 +288,11 @@ function renderMessages(messages, parentIndex, rulesMeta) {
  * @returns {string} HTML string describing the results.
  */
 function renderResults(results, rulesMeta) {
-    return lodash.map(results, (result, index) => resultTemplate({
+    return results.map((result, index) => resultTemplate({
         index,
         color: renderColor(result.errorCount, result.warningCount),
         filePath: result.filePath,
         summary: renderSummary(result.errorCount, result.warningCount)
-
     }) + renderMessages(result.messages, index, rulesMeta)).join("\n");
 }
 
index 23a142097bab52f3f1bd0f26c6b238e0c351045f..3304d429debe85e07621eb96da2e80a96c309f69 100644 (file)
@@ -15,6 +15,8 @@ const stringify = require("json-stable-stringify-without-jsonify");
 const pkg = require("../../package.json");
 const hash = require("./hash");
 
+const debug = require("debug")("eslint:lint-result-cache");
+
 //-----------------------------------------------------------------------------
 // Helpers
 //-----------------------------------------------------------------------------
@@ -22,6 +24,22 @@ const hash = require("./hash");
 const configHashCache = new WeakMap();
 const nodeVersion = process && process.version;
 
+const validCacheStrategies = ["metadata", "content"];
+const invalidCacheStrategyErrorMessage = `Cache strategy must be one of: ${validCacheStrategies
+    .map(strategy => `"${strategy}"`)
+    .join(", ")}`;
+
+/**
+ * Tests whether a provided cacheStrategy is valid
+ * @param {string} cacheStrategy The cache strategy to use
+ * @returns {boolean} true if `cacheStrategy` is one of `validCacheStrategies`; false otherwise
+ */
+function isValidCacheStrategy(cacheStrategy) {
+    return (
+        validCacheStrategies.indexOf(cacheStrategy) !== -1
+    );
+}
+
 /**
  * Calculates the hash of the config
  * @param {ConfigArray} config The config.
@@ -49,12 +67,30 @@ class LintResultCache {
     /**
      * Creates a new LintResultCache instance.
      * @param {string} cacheFileLocation The cache file location.
-     *   configuration lookup by file path).
+     * @param {"metadata" | "content"} cacheStrategy The cache strategy to use.
      */
-    constructor(cacheFileLocation) {
+    constructor(cacheFileLocation, cacheStrategy) {
         assert(cacheFileLocation, "Cache file location is required");
-
-        this.fileEntryCache = fileEntryCache.create(cacheFileLocation);
+        assert(cacheStrategy, "Cache strategy is required");
+        assert(
+            isValidCacheStrategy(cacheStrategy),
+            invalidCacheStrategyErrorMessage
+        );
+
+        debug(`Caching results to ${cacheFileLocation}`);
+
+        const useChecksum = cacheStrategy === "content";
+
+        debug(
+            `Using "${cacheStrategy}" strategy to detect changes`
+        );
+
+        this.fileEntryCache = fileEntryCache.create(
+            cacheFileLocation,
+            void 0,
+            useChecksum
+        );
+        this.cacheFileLocation = cacheFileLocation;
     }
 
     /**
@@ -76,17 +112,28 @@ class LintResultCache {
          *    was previously linted
          * If any of these are not true, we will not reuse the lint results.
          */
-
         const fileDescriptor = this.fileEntryCache.getFileDescriptor(filePath);
         const hashOfConfig = hashOfConfigFor(config);
-        const changed = fileDescriptor.changed || fileDescriptor.meta.hashOfConfig !== hashOfConfig;
+        const changed =
+            fileDescriptor.changed ||
+            fileDescriptor.meta.hashOfConfig !== hashOfConfig;
+
+        if (fileDescriptor.notFound) {
+            debug(`File not found on the file system: ${filePath}`);
+            return null;
+        }
 
-        if (fileDescriptor.notFound || changed) {
+        if (changed) {
+            debug(`Cache entry not found or no longer valid: ${filePath}`);
             return null;
         }
 
         // If source is present but null, need to reread the file from the filesystem.
-        if (fileDescriptor.meta.results && fileDescriptor.meta.results.source === null) {
+        if (
+            fileDescriptor.meta.results &&
+            fileDescriptor.meta.results.source === null
+        ) {
+            debug(`Rereading cached result source from filesystem: ${filePath}`);
             fileDescriptor.meta.results.source = fs.readFileSync(filePath, "utf-8");
         }
 
@@ -112,6 +159,7 @@ class LintResultCache {
         const fileDescriptor = this.fileEntryCache.getFileDescriptor(filePath);
 
         if (fileDescriptor && !fileDescriptor.notFound) {
+            debug(`Updating cached result: ${filePath}`);
 
             // Serialize the result, except that we want to remove the file source if present.
             const resultToSerialize = Object.assign({}, result);
@@ -135,6 +183,7 @@ class LintResultCache {
      * @returns {void}
      */
     reconcile() {
+        debug(`Persisting cached results: ${this.cacheFileLocation}`);
         this.fileEntryCache.reconcile();
     }
 }
index ce11878008f108e239dfdf3dfdf1d752e49cb340..f766764546cc5a002c4d13fddc2616b649e35494 100644 (file)
@@ -32,6 +32,7 @@ const debug = require("debug")("eslint:cli");
 /** @typedef {import("./eslint/eslint").ESLintOptions} ESLintOptions */
 /** @typedef {import("./eslint/eslint").LintMessage} LintMessage */
 /** @typedef {import("./eslint/eslint").LintResult} LintResult */
+/** @typedef {import("./options").ParsedCLIOptions} ParsedCLIOptions */
 
 //------------------------------------------------------------------------------
 // Helpers
@@ -54,7 +55,7 @@ function quietFixPredicate(message) {
 
 /**
  * Translates the CLI options into the options expected by the CLIEngine.
- * @param {Object} cliOptions The CLI options to translate.
+ * @param {ParsedCLIOptions} cliOptions The CLI options to translate.
  * @returns {ESLintOptions} The options object for the CLIEngine.
  * @private
  */
@@ -62,6 +63,7 @@ function translateOptions({
     cache,
     cacheFile,
     cacheLocation,
+    cacheStrategy,
     config,
     env,
     errorOnUnmatchedPattern,
@@ -88,6 +90,7 @@ function translateOptions({
         allowInlineConfig: inlineConfig,
         cache,
         cacheLocation: cacheLocation || cacheFile,
+        cacheStrategy,
         errorOnUnmatchedPattern,
         extensions: ext,
         fix: (fix || fixDryRun) && (quiet ? quietFixPredicate : true),
@@ -219,6 +222,8 @@ const cli = {
         if (Array.isArray(args)) {
             debug("CLI args: %o", args.slice(2));
         }
+
+        /** @type {ParsedCLIOptions} */
         let options;
 
         try {
@@ -299,12 +304,16 @@ const cli = {
             await ESLint.outputFixes(results);
         }
 
+        let resultsToPrint = results;
+
         if (options.quiet) {
             debug("Quiet mode enabled - filtering out warnings");
-            results = ESLint.getErrorResults(results);
+            resultsToPrint = ESLint.getErrorResults(resultsToPrint);
         }
 
-        if (await printResults(engine, results, options.format, options.outputFile)) {
+        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 tooManyWarnings =
                 options.maxWarnings >= 0 && warningCount > options.maxWarnings;
index 0bd7a41c6fd2bc7d620075683b5d37d74d8ea32e..ae2d2100861b4c9f8d5d495670a4db7056cabec7 100644 (file)
@@ -43,6 +43,7 @@ const { version } = require("../../package.json");
  * @property {ConfigData} [baseConfig] Base config object, extended by all configs used with this instance
  * @property {boolean} [cache] Enable result caching.
  * @property {string} [cacheLocation] The cache file to use instead of .eslintcache.
+ * @property {"metadata" | "content"} [cacheStrategy] The strategy used to detect changed files.
  * @property {string} [cwd] The value to use for the current working directory.
  * @property {boolean} [errorOnUnmatchedPattern] If `false` then `ESLint#lintFiles()` doesn't throw even if no target files found. Defaults to `true`.
  * @property {string[]} [extensions] An array of file extensions to check.
@@ -157,6 +158,7 @@ function processOptions({
     baseConfig = null,
     cache = false,
     cacheLocation = ".eslintcache",
+    cacheStrategy = "metadata",
     cwd = process.cwd(),
     errorOnUnmatchedPattern = true,
     extensions = null, // ← should be null by default because if it's an array then it suppresses RFC20 feature.
@@ -216,6 +218,12 @@ function processOptions({
     if (!isNonEmptyString(cacheLocation)) {
         errors.push("'cacheLocation' must be a non-empty string.");
     }
+    if (
+        cacheStrategy !== "metadata" &&
+        cacheStrategy !== "content"
+    ) {
+        errors.push("'cacheStrategy' must be any of \"metadata\", \"content\".");
+    }
     if (!isNonEmptyString(cwd) || !path.isAbsolute(cwd)) {
         errors.push("'cwd' must be an absolute path.");
     }
@@ -284,6 +292,7 @@ function processOptions({
         baseConfig,
         cache,
         cacheLocation,
+        cacheStrategy,
         configFile: overrideConfigFile,
         cwd,
         errorOnUnmatchedPattern,
index 0ace177aa16a425049e99679529d12baf76f8f73..054c538496f88614345fb7d49fb3b70fe10d1eab 100644 (file)
@@ -9,7 +9,7 @@
 // Requirements
 //------------------------------------------------------------------------------
 
-const lodash = require("lodash"),
+const equal = require("fast-deep-equal"),
     recConfig = require("../../conf/eslint-recommended"),
     ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"),
     { Linter } = require("../linter"),
@@ -85,7 +85,7 @@ class Registry {
      * @returns {void}
      */
     populateFromCoreRules() {
-        const rulesConfig = configRule.createCoreRuleConfigs();
+        const rulesConfig = configRule.createCoreRuleConfigs(/* noDeprecated = */ true);
 
         this.rules = makeRegistryItems(rulesConfig);
     }
@@ -329,7 +329,7 @@ function extendFromRecommended(config) {
     const recRules = Object.keys(recConfig.rules).filter(ruleId => ConfigOps.isErrorSeverity(recConfig.rules[ruleId]));
 
     recRules.forEach(ruleId => {
-        if (lodash.isEqual(recConfig.rules[ruleId], newConfig.rules[ruleId])) {
+        if (equal(recConfig.rules[ruleId], newConfig.rules[ruleId])) {
             delete newConfig.rules[ruleId];
         }
     });
index fc62b81525e66b10ce369ee544849cc1d5049fc5..4c648ac05517dfdd9e0c7ad25dafe619d6e7ac4d 100644 (file)
@@ -117,6 +117,7 @@ function writeJSConfigFile(config, filePath) {
 function write(config, filePath) {
     switch (path.extname(filePath)) {
         case ".js":
+        case ".cjs":
             writeJSConfigFile(config, filePath);
             break;
 
index 6f62e7db87e7f777ce6c4302ba7cfc0c500ea29d..3c7f2ba0944f292131970a4327ece0e79799c03b 100644 (file)
@@ -12,6 +12,7 @@
 
 const util = require("util"),
     path = require("path"),
+    fs = require("fs"),
     enquirer = require("enquirer"),
     ProgressBar = require("progress"),
     semver = require("semver"),
@@ -48,6 +49,16 @@ function writeFile(config, format) {
         extname = ".yml";
     } else if (format === "JSON") {
         extname = ".json";
+    } else if (format === "JavaScript") {
+        const pkgJSONPath = npmUtils.findPackageJson();
+
+        if (pkgJSONPath) {
+            const pkgJSONContents = JSON.parse(fs.readFileSync(pkgJSONPath, "utf8"));
+
+            if (pkgJSONContents.type === "module") {
+                extname = ".cjs";
+            }
+        }
     }
 
     const installedESLint = config.installedESLint;
@@ -531,7 +542,8 @@ function promptUser() {
             choices: [
                 { message: "Airbnb: https://github.com/airbnb/javascript", name: "airbnb" },
                 { message: "Standard: https://github.com/standard/standard", name: "standard" },
-                { message: "Google: https://github.com/google/eslint-config-google", name: "google" }
+                { message: "Google: https://github.com/google/eslint-config-google", name: "google" },
+                { message: "XO: https://github.com/xojs/eslint-config-xo", name: "xo" }
             ],
             skip() {
                 this.state.answers.packageJsonExists = npmUtils.checkPackageJson();
@@ -683,6 +695,7 @@ const init = {
     hasESLintVersionConflict,
     installModules,
     processAnswers,
+    writeFile,
     /* istanbul ignore next */initializeConfig() {
         return promptUser();
     }
index 555ea2b2b28cb269071630732d700bf5456f3afc..35191cc08764c015fcbd5c3f681abf643cbef08f 100644 (file)
@@ -172,6 +172,7 @@ function checkPackageJson(startDir) {
 module.exports = {
     installSyncSaveDev,
     fetchPeerDependencies,
+    findPackageJson,
     checkDeps,
     checkDevDeps,
     checkPackageJson
index 41d6934abba46d41466e6fc81f41d72b8510ef66..0ba69ca9cc44c73bfd8556e3298a93092404fbd9 100644 (file)
@@ -5,8 +5,6 @@
 
 "use strict";
 
-const lodash = require("lodash");
-
 /**
  * Compares the locations of two objects in a source file
  * @param {{line: number, column: number}} itemA The first object
@@ -124,7 +122,21 @@ module.exports = ({ directives, problems, reportUnusedDisableDirectives = "off"
         .map(directive => Object.assign({}, directive, { unprocessedDirective: directive }))
         .sort(compareLocations);
 
-    const lineDirectives = lodash.flatMap(directives, directive => {
+    /**
+     * 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 => {
         switch (directive.type) {
             case "disable":
             case "enable":
index 5c1a8d78aa1df02d7bb9e5561406e46bd29247db..e94b507b5dd30ab522fa6630cc435b29c55ef453 100644 (file)
@@ -15,7 +15,7 @@ const
     eslintScope = require("eslint-scope"),
     evk = require("eslint-visitor-keys"),
     espree = require("espree"),
-    lodash = require("lodash"),
+    merge = require("lodash.merge"),
     BuiltInEnvironments = require("@eslint/eslintrc/conf/environments"),
     pkg = require("../../package.json"),
     astUtils = require("../shared/ast-utils"),
@@ -444,7 +444,7 @@ function normalizeEcmaVersion(ecmaVersion) {
     return ecmaVersion >= 2015 ? ecmaVersion - 2009 : ecmaVersion;
 }
 
-const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)\*\//gu;
+const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)\*\//gsu;
 
 /**
  * Checks whether or not there is a comment which has "eslint-env *" in a given text.
@@ -529,8 +529,8 @@ function normalizeVerifyOptions(providedOptions, config) {
 function resolveParserOptions(parserName, providedOptions, enabledEnvironments) {
     const parserOptionsFromEnv = enabledEnvironments
         .filter(env => env.parserOptions)
-        .reduce((parserOptions, env) => lodash.merge(parserOptions, env.parserOptions), {});
-    const mergedParserOptions = lodash.merge(parserOptionsFromEnv, providedOptions || {});
+        .reduce((parserOptions, env) => merge(parserOptions, env.parserOptions), {});
+    const mergedParserOptions = merge(parserOptionsFromEnv, providedOptions || {});
     const isModule = mergedParserOptions.sourceType === "module";
 
     if (isModule) {
@@ -828,9 +828,10 @@ const BASE_TRAVERSAL_CONTEXT = Object.freeze(
  * @param {string} filename The reported filename of the code
  * @param {boolean} disableFixes If true, it doesn't make `fix` properties.
  * @param {string | undefined} cwd cwd of the cli
+ * @param {string} physicalFilename The full path of the file on disk without any code block information
  * @returns {Problem[]} An array of reported problems
  */
-function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parserName, settings, filename, disableFixes, cwd) {
+function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parserName, settings, filename, disableFixes, cwd, physicalFilename) {
     const emitter = createEmitter();
     const nodeQueue = [];
     let currentNode = sourceCode.ast;
@@ -859,6 +860,7 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parser
                 getDeclaredVariables: sourceCode.scopeManager.getDeclaredVariables.bind(sourceCode.scopeManager),
                 getCwd: () => cwd,
                 getFilename: () => filename,
+                getPhysicalFilename: () => physicalFilename || filename,
                 getScope: () => getScope(sourceCode.scopeManager, currentNode),
                 getSourceCode: () => sourceCode,
                 markVariableAsUsed: name => markVariableAsUsed(sourceCode.scopeManager, currentNode, parserOptions, name),
@@ -942,7 +944,9 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parser
     });
 
     // only run code path analyzer if the top level node is "Program", skip otherwise
-    const eventGenerator = nodeQueue[0].node.type === "Program" ? new CodePathAnalyzer(new NodeEventGenerator(emitter)) : new NodeEventGenerator(emitter);
+    const eventGenerator = nodeQueue[0].node.type === "Program"
+        ? new CodePathAnalyzer(new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys }))
+        : new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys });
 
     nodeQueue.forEach(traversalInfo => {
         currentNode = traversalInfo.node;
@@ -1179,7 +1183,8 @@ class Linter {
                 settings,
                 options.filename,
                 options.disableFixes,
-                slots.cwd
+                slots.cwd,
+                providedOptions.physicalFilename
             );
         } catch (err) {
             err.message += `\nOccurred while linting ${options.filename}`;
@@ -1282,9 +1287,12 @@ class Linter {
     _verifyWithProcessor(textOrSourceCode, config, options, configForRecursive) {
         const filename = options.filename || "<input>";
         const filenameToExpose = normalizeFilename(filename);
+        const physicalFilename = options.physicalFilename || filenameToExpose;
         const text = ensureText(textOrSourceCode);
         const preprocess = options.preprocess || (rawText => [rawText]);
-        const postprocess = options.postprocess || lodash.flatten;
+
+        // TODO(stephenwade): Replace this with array.flat() when we drop support for Node v10
+        const postprocess = options.postprocess || (array => [].concat(...array));
         const filterCodeBlock =
             options.filterCodeBlock ||
             (blockFilename => blockFilename.endsWith(".js"));
@@ -1306,13 +1314,13 @@ class Linter {
                 return [];
             }
 
-            // Resolve configuration again if the file extension was changed.
-            if (configForRecursive && path.extname(blockName) !== originalExtname) {
-                debug("Resolving configuration again because the file extension was changed.");
+            // Resolve configuration again if the file content or extension was changed.
+            if (configForRecursive && (text !== blockText || path.extname(blockName) !== originalExtname)) {
+                debug("Resolving configuration again because the file content or extension was changed.");
                 return this._verifyWithConfigArray(
                     blockText,
                     configForRecursive,
-                    { ...options, filename: blockName }
+                    { ...options, filename: blockName, physicalFilename }
                 );
             }
 
@@ -1320,7 +1328,7 @@ class Linter {
             return this._verifyWithoutProcessors(
                 blockText,
                 config,
-                { ...options, filename: blockName }
+                { ...options, filename: blockName, physicalFilename }
             );
         });
 
index 6f3b2513998dcd3e8de9fabb7989bca0a80e36dd..8b619fdff83e11fadd43857808062d0b2675e80a 100644 (file)
@@ -10,7 +10,6 @@
 //------------------------------------------------------------------------------
 
 const esquery = require("esquery");
-const lodash = require("lodash");
 
 //------------------------------------------------------------------------------
 // Typedefs
@@ -32,6 +31,35 @@ const lodash = require("lodash");
 // Helpers
 //------------------------------------------------------------------------------
 
+/**
+ * Computes the union of one or more arrays
+ * @param {...any[]} arrays One or more arrays to union
+ * @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))];
+}
+
+/**
+ * Computes the intersection of one or more arrays
+ * @param {...any[]} arrays One or more arrays to intersect
+ * @returns {any[]} The intersection of the input arrays
+ */
+function intersection(...arrays) {
+    if (arrays.length === 0) {
+        return [];
+    }
+
+    let result = [...new Set(arrays[0])];
+
+    for (const array of arrays.slice(1)) {
+        result = result.filter(x => array.includes(x));
+    }
+    return result;
+}
+
 /**
  * Gets the possible types of a selector
  * @param {Object} parsedSelector An object (from esquery) describing the matching behavior of the selector
@@ -46,7 +74,7 @@ function getPossibleTypes(parsedSelector) {
             const typesForComponents = parsedSelector.selectors.map(getPossibleTypes);
 
             if (typesForComponents.every(Boolean)) {
-                return lodash.union(...typesForComponents);
+                return union(...typesForComponents);
             }
             return null;
         }
@@ -63,7 +91,7 @@ function getPossibleTypes(parsedSelector) {
              * If at least one of the components could only match a particular type, the compound could only match
              * the intersection of those types.
              */
-            return lodash.intersection(...typesForComponents);
+            return intersection(...typesForComponents);
         }
 
         case "child":
@@ -166,15 +194,21 @@ function tryParseSelector(rawSelector) {
     }
 }
 
+const selectorCache = new Map();
+
 /**
  * Parses a raw selector string, and returns the parsed selector along with specificity and type information.
  * @param {string} rawSelector A raw AST selector
  * @returns {ASTSelector} A selector descriptor
  */
-const parseSelector = lodash.memoize(rawSelector => {
+function parseSelector(rawSelector) {
+    if (selectorCache.has(rawSelector)) {
+        return selectorCache.get(rawSelector);
+    }
+
     const parsedSelector = tryParseSelector(rawSelector);
 
-    return {
+    const result = {
         rawSelector,
         isExit: rawSelector.endsWith(":exit"),
         parsedSelector,
@@ -182,7 +216,10 @@ const parseSelector = lodash.memoize(rawSelector => {
         attributeCount: countClassAttributes(parsedSelector),
         identifierCount: countIdentifiers(parsedSelector)
     };
-});
+
+    selectorCache.set(rawSelector, result);
+    return result;
+}
 
 //------------------------------------------------------------------------------
 // Public Interface
@@ -208,10 +245,12 @@ class NodeEventGenerator {
      * An SafeEmitter which is the destination of events. This emitter must already
      * have registered listeners for all of the events that it needs to listen for.
      * (See lib/linter/safe-emitter.js for more details on `SafeEmitter`.)
+     * @param {ESQueryOptions} esqueryOptions `esquery` options for traversing custom nodes.
      * @returns {NodeEventGenerator} new instance
      */
-    constructor(emitter) {
+    constructor(emitter, esqueryOptions) {
         this.emitter = emitter;
+        this.esqueryOptions = esqueryOptions;
         this.currentAncestry = [];
         this.enterSelectorsByNodeType = new Map();
         this.exitSelectorsByNodeType = new Map();
@@ -250,7 +289,7 @@ class NodeEventGenerator {
      * @returns {void}
      */
     applySelector(node, selector) {
-        if (esquery.matches(node, selector.parsedSelector, this.currentAncestry)) {
+        if (esquery.matches(node, selector.parsedSelector, this.currentAncestry, this.esqueryOptions)) {
             this.emitter.emit(selector.rawSelector, node);
         }
     }
index bed5af81e5dcecef105f3f2d61a4fa55fcb41ebe..75005c16e5808992506da1a1bad6c9dbcc4f3992 100644 (file)
@@ -115,6 +115,17 @@ function normalizeReportLoc(descriptor) {
     return descriptor.node.loc;
 }
 
+/**
+ * Check that a fix has a valid range.
+ * @param {Fix|null} fix The fix to validate.
+ * @returns {void}
+ */
+function assertValidFix(fix) {
+    if (fix) {
+        assert(fix.range && typeof fix.range[0] === "number" && typeof fix.range[1] === "number", `Fix has invalid range: ${JSON.stringify(fix, null, 2)}`);
+    }
+}
+
 /**
  * Compares items in a fixes array by range.
  * @param {Fix} a The first message.
@@ -133,6 +144,10 @@ function compareFixesByRange(a, b) {
  * @returns {{text: string, range: number[]}} The merged fixes
  */
 function mergeFixes(fixes, sourceCode) {
+    for (const fix of fixes) {
+        assertValidFix(fix);
+    }
+
     if (fixes.length === 0) {
         return null;
     }
@@ -181,6 +196,8 @@ function normalizeFixes(descriptor, sourceCode) {
     if (fix && Symbol.iterator in fix) {
         return mergeFixes(Array.from(fix), sourceCode);
     }
+
+    assertValidFix(fix);
     return fix;
 }
 
index 1681f1dbd1d733fe77255df3839c1dd75dc62e89..92c140bd3c8b2eb6d4ecd6dccda49ddce7b43709 100644 (file)
 
 const optionator = require("optionator");
 
+//------------------------------------------------------------------------------
+// Typedefs
+//------------------------------------------------------------------------------
+
+/**
+ * The options object parsed by Optionator.
+ * @typedef {Object} ParsedCLIOptions
+ * @property {boolean} cache Only check changed files
+ * @property {string} cacheFile Path to the cache file. Deprecated: use --cache-location
+ * @property {string} [cacheLocation] Path to the cache file or directory
+ * @property {"metadata" | "content"} cacheStrategy Strategy to use for detecting changed files in the cache
+ * @property {boolean} [color] Force enabling/disabling of color
+ * @property {string} [config] Use this configuration, overriding .eslintrc.* config options if present
+ * @property {boolean} debug Output debugging information
+ * @property {string[]} [env] Specify environments
+ * @property {boolean} envInfo Output execution environment information
+ * @property {boolean} errorOnUnmatchedPattern Prevent errors when pattern is unmatched
+ * @property {boolean} eslintrc Disable use of configuration from .eslintrc.*
+ * @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 {string} format Use a specific output format
+ * @property {string[]} [global] Define global variables
+ * @property {boolean} [help] Show help
+ * @property {boolean} ignore Disable use of ignore files and patterns
+ * @property {string} [ignorePath] Specify path of ignore file
+ * @property {string[]} [ignorePattern] Pattern of files to ignore (in addition to those in .eslintignore)
+ * @property {boolean} init Run config initialization wizard
+ * @property {boolean} inlineConfig Prevent comments from changing config or rules
+ * @property {number} maxWarnings Number of warnings to trigger nonzero exit code
+ * @property {string} [outputFile] Specify file to write report to
+ * @property {string} [parser] Specify the parser to be used
+ * @property {Object} [parserOptions] Specify parser options
+ * @property {string[]} [plugin] Specify plugins
+ * @property {string} [printConfig] Print the configuration for the given file
+ * @property {boolean | undefined} reportUnusedDisableDirectives Adds reported errors for unused eslint-disable directives
+ * @property {string} [resolvePluginsRelativeTo] A folder where plugins should be resolved from, CWD by default
+ * @property {Object} [rule] Specify rules
+ * @property {string[]} [rulesdir] Use additional rules from this directory
+ * @property {boolean} stdin Lint code provided on <STDIN>
+ * @property {string} [stdinFilename] Specify filename to process STDIN as
+ * @property {boolean} quiet Report errors only
+ * @property {boolean} [version] Output the version number
+ * @property {string[]} _ Positional filenames or patterns
+ */
+
 //------------------------------------------------------------------------------
 // Initialization and Public Interface
 //------------------------------------------------------------------------------
@@ -214,6 +261,14 @@ module.exports = optionator({
             type: "path::String",
             description: "Path to the cache file or directory"
         },
+        {
+            option: "cache-strategy",
+            dependsOn: ["cache"],
+            type: "String",
+            default: "metadata",
+            enum: ["metadata", "content"],
+            description: "Strategy to use for detecting changed files in the cache"
+        },
         {
             heading: "Miscellaneous"
         },
index 905f3418121882cc5dfabd2f319e7edc430d077b..b08303c62b2626778541bc32be3be53a149263c7 100644 (file)
@@ -44,7 +44,8 @@ const
     assert = require("assert"),
     path = require("path"),
     util = require("util"),
-    lodash = require("lodash"),
+    merge = require("lodash.merge"),
+    equal = require("fast-deep-equal"),
     Traverser = require("../../lib/shared/traverser"),
     { getRuleOptionsSchema, validate } = require("../shared/config-validator"),
     { Linter, SourceCodeFixer, interpolate } = require("../linter");
@@ -324,10 +325,9 @@ class RuleTester {
          * configuration and the default configuration.
          * @type {Object}
          */
-        this.testerConfig = lodash.merge(
-
-            // we have to clone because merge uses the first argument for recipient
-            lodash.cloneDeep(defaultConfig),
+        this.testerConfig = merge(
+            {},
+            defaultConfig,
             testerConfig,
             { rules: { "rule-tester/validate-ast": "error" } }
         );
@@ -369,7 +369,7 @@ class RuleTester {
      * @returns {void}
      */
     static resetDefaultConfig() {
-        defaultConfig = lodash.cloneDeep(testerDefaultConfig);
+        defaultConfig = merge({}, testerDefaultConfig);
     }
 
 
@@ -427,12 +427,12 @@ class RuleTester {
             scenarioErrors = [],
             linter = this.linter;
 
-        if (lodash.isNil(test) || typeof test !== "object") {
+        if (!test || typeof test !== "object") {
             throw new TypeError(`Test Scenarios for rule ${ruleName} : Could not find test scenario object`);
         }
 
         requiredScenarios.forEach(scenarioType => {
-            if (lodash.isNil(test[scenarioType])) {
+            if (!test[scenarioType]) {
                 scenarioErrors.push(`Could not find any ${scenarioType} test scenarios`);
             }
         });
@@ -465,7 +465,7 @@ class RuleTester {
          * @private
          */
         function runRuleForItem(item) {
-            let config = lodash.cloneDeep(testerConfig),
+            let config = merge({}, testerConfig),
                 code, filename, output, beforeAST, afterAST;
 
             if (typeof item === "string") {
@@ -477,13 +477,17 @@ class RuleTester {
                  * Assumes everything on the item is a config except for the
                  * parameters used by this tester
                  */
-                const itemConfig = lodash.omit(item, RuleTesterParameters);
+                const itemConfig = { ...item };
+
+                for (const parameter of RuleTesterParameters) {
+                    delete itemConfig[parameter];
+                }
 
                 /*
                  * Create the config object from the tester config and this item
                  * specific configurations.
                  */
-                config = lodash.merge(
+                config = merge(
                     config,
                     itemConfig
                 );
@@ -589,7 +593,7 @@ class RuleTester {
          * @private
          */
         function assertASTDidntChange(beforeAST, afterAST) {
-            if (!lodash.isEqual(beforeAST, afterAST)) {
+            if (!equal(beforeAST, afterAST)) {
                 assert.fail("Rule should not modify AST.");
             }
         }
index b2167fde77bc7efb3ead27e50a4f8bf584679bef..5b8a5f0116717aa456ecb64b7f4f1c20882553e5 100644 (file)
@@ -87,17 +87,17 @@ module.exports = {
         }
 
         /**
-         * Gets the closing parenthesis which is the pair of the given opening parenthesis.
-         * @param {Token} token The opening parenthesis token to get.
+         * Gets the closing parenthesis by the given node.
+         * @param {ASTNode} node first node after an opening parenthesis.
          * @returns {Token} The found closing parenthesis token.
          */
-        function findClosingParen(token) {
-            let node = sourceCode.getNodeByRangeIndex(token.range[0]);
+        function findClosingParen(node) {
+            let nodeToCheck = node;
 
-            while (!astUtils.isParenthesised(sourceCode, node)) {
-                node = node.parent;
+            while (!astUtils.isParenthesised(sourceCode, nodeToCheck)) {
+                nodeToCheck = nodeToCheck.parent;
             }
-            return sourceCode.getTokenAfter(node);
+            return sourceCode.getTokenAfter(nodeToCheck);
         }
 
         /**
@@ -226,12 +226,22 @@ module.exports = {
                             const arrowToken = sourceCode.getTokenBefore(arrowBody, astUtils.isArrowToken);
                             const [firstTokenAfterArrow, secondTokenAfterArrow] = sourceCode.getTokensAfter(arrowToken, { count: 2 });
                             const lastToken = sourceCode.getLastToken(node);
-                            const isParenthesisedObjectLiteral =
+
+                            let parenthesisedObjectLiteral = null;
+
+                            if (
                                 astUtils.isOpeningParenToken(firstTokenAfterArrow) &&
-                                astUtils.isOpeningBraceToken(secondTokenAfterArrow);
+                                astUtils.isOpeningBraceToken(secondTokenAfterArrow)
+                            ) {
+                                const braceNode = sourceCode.getNodeByRangeIndex(secondTokenAfterArrow.range[0]);
+
+                                if (braceNode.type === "ObjectExpression") {
+                                    parenthesisedObjectLiteral = braceNode;
+                                }
+                            }
 
                             // If the value is object literal, remove parentheses which were forced by syntax.
-                            if (isParenthesisedObjectLiteral) {
+                            if (parenthesisedObjectLiteral) {
                                 const openingParenToken = firstTokenAfterArrow;
                                 const openingBraceToken = secondTokenAfterArrow;
 
@@ -247,7 +257,7 @@ module.exports = {
                                 }
 
                                 // Closing paren for the object doesn't have to be lastToken, e.g.: () => ({}).foo()
-                                fixes.push(fixer.remove(findClosingParen(openingBraceToken)));
+                                fixes.push(fixer.remove(findClosingParen(parenthesisedObjectLiteral)));
                                 fixes.push(fixer.insertTextAfter(lastToken, "}"));
 
                             } else {
index e22b7f3551e114a07fc0704e01442557b1846575..798c111ec3b6b61555d0546d22315ce4800d1fc2 100644 (file)
@@ -9,7 +9,6 @@
 // Requirements
 //------------------------------------------------------------------------------
 
-const lodash = require("lodash");
 const astUtils = require("./utils/ast-utils");
 
 //------------------------------------------------------------------------------
@@ -144,23 +143,33 @@ module.exports = {
          * @returns {ASTNode|null} The last node or null.
          */
         function getLastItem(node) {
+
+            /**
+             * Returns the last element of an array
+             * @param {any[]} array The input array
+             * @returns {any} The last element
+             */
+            function last(array) {
+                return array[array.length - 1];
+            }
+
             switch (node.type) {
                 case "ObjectExpression":
                 case "ObjectPattern":
-                    return lodash.last(node.properties);
+                    return last(node.properties);
                 case "ArrayExpression":
                 case "ArrayPattern":
-                    return lodash.last(node.elements);
+                    return last(node.elements);
                 case "ImportDeclaration":
                 case "ExportNamedDeclaration":
-                    return lodash.last(node.specifiers);
+                    return last(node.specifiers);
                 case "FunctionDeclaration":
                 case "FunctionExpression":
                 case "ArrowFunctionExpression":
-                    return lodash.last(node.params);
+                    return last(node.params);
                 case "CallExpression":
                 case "NewExpression":
-                    return lodash.last(node.arguments);
+                    return last(node.arguments);
                 default:
                     return null;
             }
@@ -316,7 +325,7 @@ module.exports = {
             "always-multiline": forceTrailingCommaIfMultiline,
             "only-multiline": allowTrailingCommaIfMultiline,
             never: forbidTrailingComma,
-            ignore: lodash.noop
+            ignore: () => {}
         };
 
         return {
index 73c10a7711b904b9ddbd82855d11fa5e352f3263..2bf41a00bb66d8999fcc7add510a8d1e1a437bcd 100644 (file)
@@ -181,7 +181,7 @@ module.exports = {
 
                     validateCommaItemSpacing({
                         comma: token,
-                        left: astUtils.isCommaToken(previousToken) || commaTokensToIgnore.indexOf(token) > -1 ? null : previousToken,
+                        left: astUtils.isCommaToken(previousToken) || commaTokensToIgnore.includes(token) ? null : previousToken,
                         right: astUtils.isCommaToken(nextToken) ? null : nextToken
                     }, token);
                 });
index 5d62c6ff44b8f1a3e68f8ce830b36643fabc411c..116c8ad0a63ee9b10039080b38e3abbaba0983e4 100644 (file)
@@ -10,9 +10,8 @@
 // Requirements
 //------------------------------------------------------------------------------
 
-const lodash = require("lodash");
-
 const astUtils = require("./utils/ast-utils");
+const { upperCaseFirst } = require("../shared/string-utils");
 
 //------------------------------------------------------------------------------
 // Rule Definition
@@ -95,7 +94,7 @@ module.exports = {
          * @private
          */
         function endFunction(node) {
-            const name = lodash.upperFirst(astUtils.getFunctionNameWithKind(node));
+            const name = upperCaseFirst(astUtils.getFunctionNameWithKind(node));
             const complexity = fns.pop();
 
             if (complexity > THRESHOLD) {
index 94db253d25bacb654e2f4cb67102f3d8321a7d27..a250430cb766b5677022626cfe6a4239e82aadc7 100644 (file)
@@ -8,8 +8,8 @@
 // Requirements
 //------------------------------------------------------------------------------
 
-const lodash = require("lodash");
 const astUtils = require("./utils/ast-utils");
+const { upperCaseFirst } = require("../shared/string-utils");
 
 //------------------------------------------------------------------------------
 // Helpers
@@ -164,7 +164,7 @@ module.exports = {
                     funcInfo.data = {
                         name: funcInfo.node.type === "Program"
                             ? "Program"
-                            : lodash.upperFirst(astUtils.getFunctionNameWithKind(funcInfo.node))
+                            : upperCaseFirst(astUtils.getFunctionNameWithKind(funcInfo.node))
                     };
                 } else if (funcInfo.hasReturnValue !== hasReturnValue) {
                     context.report({
index fbba6c8f5e8be58d5628ff906a5cc0d299aa9cda..24b0c9279c71ca74d56c58e0b70954a65fa72a58 100644 (file)
@@ -4,12 +4,6 @@
  */
 "use strict";
 
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const lodash = require("lodash");
-
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
@@ -48,13 +42,14 @@ module.exports = {
             Program: function checkBadEOF(node) {
                 const sourceCode = context.getSourceCode(),
                     src = sourceCode.getText(),
+                    lastLine = sourceCode.lines[sourceCode.lines.length - 1],
                     location = {
-                        column: lodash.last(sourceCode.lines).length,
+                        column: lastLine.length,
                         line: sourceCode.lines.length
                     },
                     LF = "\n",
                     CRLF = `\r${LF}`,
-                    endsWithNewline = lodash.endsWith(src, LF);
+                    endsWithNewline = src.endsWith(LF);
 
                 /*
                  * Empty source is always valid: No content in file so we don't
index 50010d3f7acb16dfc182501d3756813d937c5d1c..a26ee87b1305ed8e79362f86b26a9ccd760b1ca5 100644 (file)
@@ -954,7 +954,7 @@ module.exports = {
         }
 
         /**
-         * Checks wether a return statement is wrapped in ()
+         * Checks whether a return statement is wrapped in ()
          * @param {ASTNode} node node to examine
          * @returns {boolean} the result
          */
index 1c0dccc5c9891f3e0d7a82ce20c9713a6680cc3d..b1af2a73b33be262230525b0a1f5e18ce9b48d5c 100644 (file)
 // Requirements
 //------------------------------------------------------------------------------
 
-const lodash = require("lodash");
-const astUtils = require("./utils/ast-utils");
 const createTree = require("functional-red-black-tree");
 
+const astUtils = require("./utils/ast-utils");
+
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
@@ -1068,7 +1068,7 @@ module.exports = {
         const baseOffsetListeners = {
             "ArrayExpression, ArrayPattern"(node) {
                 const openingBracket = sourceCode.getFirstToken(node);
-                const closingBracket = sourceCode.getTokenAfter(lodash.findLast(node.elements) || openingBracket, astUtils.isClosingBracketToken);
+                const closingBracket = sourceCode.getTokenAfter([...node.elements].reverse().find(_ => _) || openingBracket, astUtils.isClosingBracketToken);
 
                 addElementListIndent(node.elements, openingBracket, closingBracket, options.ArrayExpression);
             },
@@ -1178,6 +1178,7 @@ module.exports = {
                     offsets.setDesiredOffset(colonToken, firstToken, 1);
 
                     offsets.setDesiredOffset(firstConsequentToken, firstToken,
+                        firstConsequentToken.type === "Punctuator" &&
                         options.offsetTernaryExpressions ? 2 : 1);
 
                     /*
@@ -1559,8 +1560,9 @@ module.exports = {
          * 2. Don't set any offsets against the first token of the node.
          * 3. Call `ignoreNode` on the node sometime after exiting it and before validating offsets.
          */
-        const offsetListeners = lodash.mapValues(
-            baseOffsetListeners,
+        const offsetListeners = {};
+
+        for (const [selector, listener] of Object.entries(baseOffsetListeners)) {
 
             /*
              * Offset listener calls are deferred until traversal is finished, and are called as
@@ -1578,10 +1580,8 @@ module.exports = {
              * To avoid this, the `Identifier` listener isn't called until traversal finishes and all
              * ignored nodes are known.
              */
-            listener =>
-                node =>
-                    listenerCallQueue.push({ listener, node })
-        );
+            offsetListeners[selector] = node => listenerCallQueue.push({ listener, node });
+        }
 
         // For each ignored node selector, set up a listener to collect it into the `ignoredNodes` set.
         const ignoredNodes = new Set();
index 5e1b83cdd5d24c48e16614881e0fe2116e2ea0dd..6806e793cd193b41dc1fa63162534d9ca9e94b8a 100644 (file)
@@ -8,8 +8,7 @@
 // Requirements
 //------------------------------------------------------------------------------
 
-const lodash = require("lodash"),
-    astUtils = require("./utils/ast-utils");
+const astUtils = require("./utils/ast-utils");
 
 //------------------------------------------------------------------------------
 // Helpers
@@ -347,7 +346,7 @@ module.exports = {
             const nextTokenOrComment = sourceCode.getTokenAfter(token, { includeComments: true });
 
             // check for newline before
-            if (!exceptionStartAllowed && before && !lodash.includes(commentAndEmptyLines, prevLineNum) &&
+            if (!exceptionStartAllowed && before && !commentAndEmptyLines.includes(prevLineNum) &&
                     !(astUtils.isCommentToken(previousTokenOrComment) && astUtils.isTokenOnSameLine(previousTokenOrComment, token))) {
                 const lineStart = token.range[0] - token.loc.start.column;
                 const range = [lineStart, lineStart];
@@ -362,7 +361,7 @@ module.exports = {
             }
 
             // check for newline after
-            if (!exceptionEndAllowed && after && !lodash.includes(commentAndEmptyLines, nextLineNum) &&
+            if (!exceptionEndAllowed && after && !commentAndEmptyLines.includes(nextLineNum) &&
                     !(astUtils.isCommentToken(nextTokenOrComment) && astUtils.isTokenOnSameLine(token, nextTokenOrComment))) {
                 context.report({
                     node: token,
index aa423a60a1faaa3c6b1dba20cf50d47e7db4d4a9..60e2e879f54df053b0ad4d9bf28ee5f56539c11a 100644 (file)
@@ -9,8 +9,7 @@
 //------------------------------------------------------------------------------
 
 const astUtils = require("./utils/ast-utils");
-
-const lodash = require("lodash");
+const { upperCaseFirst } = require("../shared/string-utils");
 
 //------------------------------------------------------------------------------
 // Constants
@@ -71,7 +70,7 @@ module.exports = {
         type: "suggestion",
 
         docs: {
-            description: "enforce a maximum number of line of code in a function",
+            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"
@@ -191,7 +190,7 @@ module.exports = {
             }
 
             if (lineCount > maxLines) {
-                const name = lodash.upperFirst(astUtils.getFunctionNameWithKind(funcNode));
+                const name = upperCaseFirst(astUtils.getFunctionNameWithKind(funcNode));
 
                 context.report({
                     node,
index 0c7e761fa613b4b3400a8a7b5f62bb7cc94c41a6..8bd5a1c95f4043a46995eda3ca043e62f07b8fb0 100644 (file)
@@ -8,9 +8,22 @@
 // Requirements
 //------------------------------------------------------------------------------
 
-const lodash = require("lodash");
 const astUtils = require("./utils/ast-utils");
 
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Creates an array of numbers from `start` up to, but not including, `end`
+ * @param {number} start The start of the range
+ * @param {number} end The end of the range
+ * @returns {number[]} The range of numbers
+ */
+function range(start, end) {
+    return [...Array(end - start).keys()].map(x => x + start);
+}
+
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
@@ -119,11 +132,25 @@ module.exports = {
             }
 
             if (start <= end) {
-                return lodash.range(start, end + 1);
+                return range(start, end + 1);
             }
             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) => ({
@@ -135,7 +162,7 @@ module.exports = {
                  * If file ends with a linebreak, `sourceCode.lines` will have one extra empty line at the end.
                  * That isn't a real line, so we shouldn't count it.
                  */
-                if (lines.length > 1 && lodash.last(lines).text === "") {
+                if (lines.length > 1 && lines[lines.length - 1].text === "") {
                     lines.pop();
                 }
 
@@ -146,12 +173,10 @@ module.exports = {
                 if (skipComments) {
                     const comments = sourceCode.getAllComments();
 
-                    const commentLines = lodash.flatten(
-                        comments.map(comment => getLinesWithoutCode(comment))
-                    );
+                    const commentLines = flatMap(comments, comment => getLinesWithoutCode(comment));
 
                     lines = lines.filter(
-                        l => !lodash.includes(commentLines, l.lineNumber)
+                        l => !commentLines.includes(l.lineNumber)
                     );
                 }
 
@@ -163,7 +188,7 @@ module.exports = {
                         },
                         end: {
                             line: sourceCode.lines.length,
-                            column: lodash.last(sourceCode.lines).length
+                            column: sourceCode.lines[sourceCode.lines.length - 1].length
                         }
                     };
 
index 4eebe2d95a31e31ecf231e4a9c863a18f3404b04..8fb798401cb8d4fb9ba353c866b744a6f0a469d2 100644 (file)
@@ -9,9 +9,8 @@
 // Requirements
 //------------------------------------------------------------------------------
 
-const lodash = require("lodash");
-
 const astUtils = require("./utils/ast-utils");
+const { upperCaseFirst } = require("../shared/string-utils");
 
 //------------------------------------------------------------------------------
 // Rule Definition
@@ -85,7 +84,7 @@ module.exports = {
                     node,
                     messageId: "exceed",
                     data: {
-                        name: lodash.upperFirst(astUtils.getFunctionNameWithKind(node)),
+                        name: upperCaseFirst(astUtils.getFunctionNameWithKind(node)),
                         count: node.params.length,
                         max: numParams
                     }
index 437b393a5082f4e5d9ac8c285d1b59e45e38b58a..65d5539550d37a7a48e6cc90c76320338db2563d 100644 (file)
@@ -9,9 +9,8 @@
 // Requirements
 //------------------------------------------------------------------------------
 
-const lodash = require("lodash");
-
 const astUtils = require("./utils/ast-utils");
+const { upperCaseFirst } = require("../shared/string-utils");
 
 //------------------------------------------------------------------------------
 // Rule Definition
@@ -97,7 +96,7 @@ module.exports = {
          */
         function reportIfTooManyStatements(node, count, max) {
             if (count > max) {
-                const name = lodash.upperFirst(astUtils.getFunctionNameWithKind(node));
+                const name = upperCaseFirst(astUtils.getFunctionNameWithKind(node));
 
                 context.report({
                     node,
index 7218dc64add343f12703e3ea9f9a1a19c0cf894e..cc3da1d5a68080adf9408cd72701cf95f5110522 100644 (file)
  */
 "use strict";
 
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const NAMED_TYPES = ["ImportSpecifier", "ExportSpecifier"];
+const NAMESPACE_TYPES = [
+    "ImportNamespaceSpecifier",
+    "ExportNamespaceSpecifier"
+];
+
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
 
 /**
- * Returns the name of the module imported or re-exported.
+ * Check if an import/export type belongs to (ImportSpecifier|ExportSpecifier) or (ImportNamespaceSpecifier|ExportNamespaceSpecifier).
+ * @param {string} importExportType An import/export type to check.
+ * @param {string} type Can be "named" or "namespace"
+ * @returns {boolean} True if import/export type belongs to (ImportSpecifier|ExportSpecifier) or (ImportNamespaceSpecifier|ExportNamespaceSpecifier) and false if it doesn't.
+ */
+function isImportExportSpecifier(importExportType, type) {
+    const arrayToCheck = type === "named" ? NAMED_TYPES : NAMESPACE_TYPES;
+
+    return arrayToCheck.includes(importExportType);
+}
+
+/**
+ * Return the type of (import|export).
  * @param {ASTNode} node A node to get.
- * @returns {string} the name of the module, or empty string if no name.
+ * @returns {string} The type of the (import|export).
  */
-function getValue(node) {
-    if (node && node.source && node.source.value) {
-        return node.source.value.trim();
+function getImportExportType(node) {
+    if (node.specifiers && node.specifiers.length > 0) {
+        const nodeSpecifiers = node.specifiers;
+        const index = nodeSpecifiers.findIndex(
+            ({ type }) =>
+                isImportExportSpecifier(type, "named") ||
+                isImportExportSpecifier(type, "namespace")
+        );
+        const i = index > -1 ? index : 0;
+
+        return nodeSpecifiers[i].type;
     }
+    if (node.type === "ExportAllDeclaration") {
+        if (node.exported) {
+            return "ExportNamespaceSpecifier";
+        }
+        return "ExportAll";
+    }
+    return "SideEffectImport";
+}
 
-    return "";
+/**
+ * Returns a boolean indicates if two (import|export) can be merged
+ * @param {ASTNode} node1 A node to check.
+ * @param {ASTNode} node2 A node to check.
+ * @returns {boolean} True if two (import|export) can be merged, false if they can't.
+ */
+function isImportExportCanBeMerged(node1, node2) {
+    const importExportType1 = getImportExportType(node1);
+    const importExportType2 = getImportExportType(node2);
+
+    if (
+        (importExportType1 === "ExportAll" &&
+            importExportType2 !== "ExportAll" &&
+            importExportType2 !== "SideEffectImport") ||
+        (importExportType1 !== "ExportAll" &&
+            importExportType1 !== "SideEffectImport" &&
+            importExportType2 === "ExportAll")
+    ) {
+        return false;
+    }
+    if (
+        (isImportExportSpecifier(importExportType1, "namespace") &&
+            isImportExportSpecifier(importExportType2, "named")) ||
+        (isImportExportSpecifier(importExportType2, "namespace") &&
+            isImportExportSpecifier(importExportType1, "named"))
+    ) {
+        return false;
+    }
+    return true;
 }
 
 /**
- * Checks if the name of the import or export exists in the given array, and reports if so.
- * @param {RuleContext} context The ESLint rule context object.
- * @param {ASTNode} node A node to get.
- * @param {string} value The name of the imported or exported module.
- * @param {string[]} array The array containing other imports or exports in the file.
- * @param {string} messageId A messageId to be reported after the name of the module
- *
- * @returns {void} No return value
+ * Returns a boolean if we should report (import|export).
+ * @param {ASTNode} node A node to be reported or not.
+ * @param {[ASTNode]} previousNodes An array contains previous nodes of the module imported or exported.
+ * @returns {boolean} True if the (import|export) should be reported.
  */
-function checkAndReport(context, node, value, array, messageId) {
-    if (array.indexOf(value) !== -1) {
-        context.report({
-            node,
-            messageId,
-            data: {
-                module: value
-            }
-        });
+function shouldReportImportExport(node, previousNodes) {
+    let i = 0;
+
+    while (i < previousNodes.length) {
+        if (isImportExportCanBeMerged(node, previousNodes[i])) {
+            return true;
+        }
+        i++;
     }
+    return false;
 }
 
 /**
- * @callback nodeCallback
- * @param {ASTNode} node A node to handle.
+ * Returns array contains only nodes with declarations types equal to type.
+ * @param {[{node: ASTNode, declarationType: string}]} nodes An array contains objects, each object contains a node and a declaration type.
+ * @param {string} type Declaration type.
+ * @returns {[ASTNode]} An array contains only nodes with declarations types equal to type.
+ */
+function getNodesByDeclarationType(nodes, type) {
+    return nodes
+        .filter(({ declarationType }) => declarationType === type)
+        .map(({ node }) => node);
+}
+
+/**
+ * Returns the name of the module imported or re-exported.
+ * @param {ASTNode} node A node to get.
+ * @returns {string} The name of the module, or empty string if no name.
  */
+function getModule(node) {
+    if (node && node.source && node.source.value) {
+        return node.source.value.trim();
+    }
+    return "";
+}
 
 /**
- * Returns a function handling the imports of a given file
+ * Checks if the (import|export) can be merged with at least one import or one export, and reports if so.
  * @param {RuleContext} context The ESLint rule context object.
+ * @param {ASTNode} node A node to get.
+ * @param {Map} modules A Map object contains as a key a module name and as value an array contains objects, each object contains a node and a declaration type.
+ * @param {string} declarationType A declaration type can be an import or export.
  * @param {boolean} includeExports Whether or not to check for exports in addition to imports.
- * @param {string[]} importsInFile The array containing other imports in the file.
- * @param {string[]} exportsInFile The array containing other exports in the file.
- *
- * @returns {nodeCallback} A function passed to ESLint to handle the statement.
+ * @returns {void} No return value.
  */
-function handleImports(context, includeExports, importsInFile, exportsInFile) {
-    return function(node) {
-        const value = getValue(node);
+function checkAndReport(
+    context,
+    node,
+    modules,
+    declarationType,
+    includeExports
+) {
+    const module = getModule(node);
 
-        if (value) {
-            checkAndReport(context, node, value, importsInFile, "import");
+    if (modules.has(module)) {
+        const previousNodes = modules.get(module);
+        const messagesIds = [];
+        const importNodes = getNodesByDeclarationType(previousNodes, "import");
+        let exportNodes;
 
+        if (includeExports) {
+            exportNodes = getNodesByDeclarationType(previousNodes, "export");
+        }
+        if (declarationType === "import") {
+            if (shouldReportImportExport(node, importNodes)) {
+                messagesIds.push("import");
+            }
             if (includeExports) {
-                checkAndReport(context, node, value, exportsInFile, "importAs");
+                if (shouldReportImportExport(node, exportNodes)) {
+                    messagesIds.push("importAs");
+                }
+            }
+        } else if (declarationType === "export") {
+            if (shouldReportImportExport(node, exportNodes)) {
+                messagesIds.push("export");
+            }
+            if (shouldReportImportExport(node, importNodes)) {
+                messagesIds.push("exportAs");
             }
-
-            importsInFile.push(value);
         }
-    };
+        messagesIds.forEach(messageId =>
+            context.report({
+                node,
+                messageId,
+                data: {
+                    module
+                }
+            }));
+    }
 }
 
 /**
- * Returns a function handling the exports of a given file
+ * @callback nodeCallback
+ * @param {ASTNode} node A node to handle.
+ */
+
+/**
+ * Returns a function handling the (imports|exports) of a given file
  * @param {RuleContext} context The ESLint rule context object.
- * @param {string[]} importsInFile The array containing other imports in the file.
- * @param {string[]} exportsInFile The array containing other exports in the file.
- *
+ * @param {Map} modules A Map object contains as a key a module name and as value an array contains objects, each object contains a node and a declaration type.
+ * @param {string} declarationType A declaration type can be an import or export.
+ * @param {boolean} includeExports Whether or not to check for exports in addition to imports.
  * @returns {nodeCallback} A function passed to ESLint to handle the statement.
  */
-function handleExports(context, importsInFile, exportsInFile) {
+function handleImportsExports(
+    context,
+    modules,
+    declarationType,
+    includeExports
+) {
     return function(node) {
-        const value = getValue(node);
+        const module = getModule(node);
+
+        if (module) {
+            checkAndReport(
+                context,
+                node,
+                modules,
+                declarationType,
+                includeExports
+            );
+            const currentNode = { node, declarationType };
+            let nodes = [currentNode];
 
-        if (value) {
-            checkAndReport(context, node, value, exportsInFile, "export");
-            checkAndReport(context, node, value, importsInFile, "exportAs");
+            if (modules.has(module)) {
+                const previousNodes = modules.get(module);
 
-            exportsInFile.push(value);
+                nodes = [...previousNodes, currentNode];
+            }
+            modules.set(module, nodes);
         }
     };
 }
@@ -105,16 +238,19 @@ module.exports = {
             url: "https://eslint.org/docs/rules/no-duplicate-imports"
         },
 
-        schema: [{
-            type: "object",
-            properties: {
-                includeExports: {
-                    type: "boolean",
-                    default: false
-                }
-            },
-            additionalProperties: false
-        }],
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    includeExports: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
+
         messages: {
             import: "'{{module}}' import is duplicated.",
             importAs: "'{{module}}' import is duplicated as export.",
@@ -125,18 +261,30 @@ module.exports = {
 
     create(context) {
         const includeExports = (context.options[0] || {}).includeExports,
-            importsInFile = [],
-            exportsInFile = [];
-
+            modules = new Map();
         const handlers = {
-            ImportDeclaration: handleImports(context, includeExports, importsInFile, exportsInFile)
+            ImportDeclaration: handleImportsExports(
+                context,
+                modules,
+                "import",
+                includeExports
+            )
         };
 
         if (includeExports) {
-            handlers.ExportNamedDeclaration = handleExports(context, importsInFile, exportsInFile);
-            handlers.ExportAllDeclaration = handleExports(context, importsInFile, exportsInFile);
+            handlers.ExportNamedDeclaration = handleImportsExports(
+                context,
+                modules,
+                "export",
+                includeExports
+            );
+            handlers.ExportAllDeclaration = handleImportsExports(
+                context,
+                modules,
+                "export",
+                includeExports
+            );
         }
-
         return handlers;
     }
 };
index 19c6fced79d4ffa48869f43654c7894d7d45ed0f..307e340c958d880d060d5c19648618fcc2492ad5 100644 (file)
@@ -844,45 +844,49 @@ module.exports = {
             ExportDefaultDeclaration: node => checkExpressionOrExportStatement(node.declaration),
             ExpressionStatement: node => checkExpressionOrExportStatement(node.expression),
 
-            "ForInStatement, ForOfStatement"(node) {
-                if (node.left.type !== "VariableDeclarator") {
+            ForInStatement(node) {
+                if (node.left.type !== "VariableDeclaration") {
                     const firstLeftToken = sourceCode.getFirstToken(node.left, astUtils.isNotOpeningParenToken);
 
                     if (
-                        firstLeftToken.value === "let" && (
-
-                            /*
-                             * If `let` is the only thing on the left side of the loop, it's the loop variable: `for ((let) of foo);`
-                             * Removing it will cause a syntax error, because it will be parsed as the start of a VariableDeclarator.
-                             */
-                            (firstLeftToken.range[1] === node.left.range[1] || /*
-                             * If `let` is followed by a `[` token, it's a property access on the `let` value: `for ((let[foo]) of bar);`
-                             * Removing it will cause the property access to be parsed as a destructuring declaration of `foo` instead.
-                             */
-                            astUtils.isOpeningBracketToken(
-                                sourceCode.getTokenAfter(firstLeftToken, astUtils.isNotClosingParenToken)
-                            ))
+                        firstLeftToken.value === "let" &&
+                        astUtils.isOpeningBracketToken(
+                            sourceCode.getTokenAfter(firstLeftToken, astUtils.isNotClosingParenToken)
                         )
                     ) {
+
+                        // ForInStatement#left expression cannot start with `let[`.
                         tokensToIgnore.add(firstLeftToken);
                     }
                 }
 
-                if (node.type === "ForOfStatement") {
-                    const hasExtraParens = node.right.type === "SequenceExpression"
-                        ? hasDoubleExcessParens(node.right)
-                        : hasExcessParens(node.right);
+                if (hasExcessParens(node.left)) {
+                    report(node.left);
+                }
 
-                    if (hasExtraParens) {
-                        report(node.right);
-                    }
-                } else if (hasExcessParens(node.right)) {
+                if (hasExcessParens(node.right)) {
                     report(node.right);
                 }
+            },
+
+            ForOfStatement(node) {
+                if (node.left.type !== "VariableDeclaration") {
+                    const firstLeftToken = sourceCode.getFirstToken(node.left, astUtils.isNotOpeningParenToken);
+
+                    if (firstLeftToken.value === "let") {
+
+                        // ForOfStatement#left expression cannot start with `let`.
+                        tokensToIgnore.add(firstLeftToken);
+                    }
+                }
 
                 if (hasExcessParens(node.left)) {
                     report(node.left);
                 }
+
+                if (hasExcessParensWithPrecedence(node.right, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
+                    report(node.right);
+                }
             },
 
             ForStatement(node) {
index dd1f3ed9d9a74e1b80cec1eeeccfdb295c33b939..e8016e93e59bc908aa13b761f45557b4704ab07c 100644 (file)
@@ -4,12 +4,6 @@
  */
 "use strict";
 
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const lodash = require("lodash");
-
 //------------------------------------------------------------------------------
 // Helpers
 //------------------------------------------------------------------------------
@@ -25,7 +19,7 @@ const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/iu;
  */
 function hasFallthroughComment(node, context, fallthroughCommentPattern) {
     const sourceCode = context.getSourceCode();
-    const comment = lodash.last(sourceCode.getCommentsBefore(node));
+    const comment = sourceCode.getCommentsBefore(node).pop();
 
     return Boolean(comment && fallthroughCommentPattern.test(comment.value));
 }
@@ -133,7 +127,7 @@ module.exports = {
                  */
                 if (currentCodePath.currentSegments.some(isReachable) &&
                     (node.consequent.length > 0 || hasBlankLinesBetween(node, nextToken)) &&
-                    lodash.last(node.parent.cases) !== node) {
+                    node.parent.cases[node.parent.cases.length - 1] !== node) {
                     fallthroughCase = node;
                 }
             }
index a639711ecea1558b7abd2850bc47a951f0ba4355..993b8d1f1c8d4457dacddcda450a6b251cca3a21 100644 (file)
@@ -24,6 +24,7 @@ function parseOptions(options) {
         boolean: "boolean" in options ? options.boolean : true,
         number: "number" in options ? options.number : true,
         string: "string" in options ? options.string : true,
+        disallowTemplateShorthand: "disallowTemplateShorthand" in options ? options.disallowTemplateShorthand : false,
         allow: options.allow || []
     };
 }
@@ -108,6 +109,20 @@ function getNonNumericOperand(node) {
     return null;
 }
 
+/**
+ * Checks whether an expression evaluates to a string.
+ * @param {ASTNode} node node that represents the expression to check.
+ * @returns {boolean} Whether or not the expression evaluates to a string.
+ */
+function isStringType(node) {
+    return astUtils.isStringLiteral(node) ||
+        (
+            node.type === "CallExpression" &&
+            node.callee.type === "Identifier" &&
+            node.callee.name === "String"
+        );
+}
+
 /**
  * Checks whether a node is an empty string literal or not.
  * @param {ASTNode} node The node to check.
@@ -125,8 +140,8 @@ function isEmptyString(node) {
  */
 function isConcatWithEmptyString(node) {
     return node.operator === "+" && (
-        (isEmptyString(node.left) && !astUtils.isStringLiteral(node.right)) ||
-        (isEmptyString(node.right) && !astUtils.isStringLiteral(node.left))
+        (isEmptyString(node.left) && !isStringType(node.right)) ||
+        (isEmptyString(node.right) && !isStringType(node.left))
     );
 }
 
@@ -180,6 +195,10 @@ module.exports = {
                     type: "boolean",
                     default: true
                 },
+                disallowTemplateShorthand: {
+                    type: "boolean",
+                    default: false
+                },
                 allow: {
                     type: "array",
                     items: {
@@ -299,6 +318,43 @@ module.exports = {
 
                     report(node, recommendation, true);
                 }
+            },
+
+            TemplateLiteral(node) {
+                if (!options.disallowTemplateShorthand) {
+                    return;
+                }
+
+                // tag`${foo}`
+                if (node.parent.type === "TaggedTemplateExpression") {
+                    return;
+                }
+
+                // `` or `${foo}${bar}`
+                if (node.expressions.length !== 1) {
+                    return;
+                }
+
+
+                //  `prefix${foo}`
+                if (node.quasis[0].value.cooked !== "") {
+                    return;
+                }
+
+                //  `${foo}postfix`
+                if (node.quasis[1].value.cooked !== "") {
+                    return;
+                }
+
+                // if the expression is already a string, then this isn't a coercion
+                if (isStringType(node.expressions[0])) {
+                    return;
+                }
+
+                const code = sourceCode.getText(node.expressions[0]);
+                const recommendation = `String(${code})`;
+
+                report(node, recommendation, true);
             }
         };
     }
index 6136ebb9e0be11a90e881538c38bc6305859eb8f..94ad5ba6d5c23db14c2564ff059db94e797dfbb7 100644 (file)
@@ -69,6 +69,28 @@ module.exports = {
             return node && node.type === "Literal" && typeof node.value === "string";
         }
 
+        /**
+         * Gets flags of a regular expression created by the given `RegExp()` or `new RegExp()` call
+         * Examples:
+         *     new RegExp(".")         // => ""
+         *     new RegExp(".", "gu")   // => "gu"
+         *     new RegExp(".", flags)  // => null
+         * @param {ASTNode} node `CallExpression` or `NewExpression` node
+         * @returns {string|null} flags if they can be determined, `null` otherwise
+         * @private
+         */
+        function getFlags(node) {
+            if (node.arguments.length < 2) {
+                return "";
+            }
+
+            if (isString(node.arguments[1])) {
+                return node.arguments[1].value;
+            }
+
+            return null;
+        }
+
         /**
          * Check syntax error in a given pattern.
          * @param {string} pattern The RegExp pattern to validate.
@@ -104,18 +126,23 @@ module.exports = {
                     return;
                 }
                 const pattern = node.arguments[0].value;
-                let flags = isString(node.arguments[1]) ? node.arguments[1].value : "";
+                let flags = getFlags(node);
 
-                if (allowedFlags) {
+                if (flags && allowedFlags) {
                     flags = flags.replace(allowedFlags, "");
                 }
 
-                // If flags are unknown, check both are errored or not.
-                const message = validateRegExpFlags(flags) || (
-                    flags
-                        ? validateRegExpPattern(pattern, flags.indexOf("u") !== -1)
-                        : validateRegExpPattern(pattern, true) && validateRegExpPattern(pattern, false)
-                );
+                const message =
+                    (
+                        flags && validateRegExpFlags(flags)
+                    ) ||
+                    (
+
+                        // If flags are unknown, report the regex only if its pattern is invalid both with and without the "u" flag
+                        flags === null
+                            ? validateRegExpPattern(pattern, true) && validateRegExpPattern(pattern, false)
+                            : validateRegExpPattern(pattern, flags.includes("u"))
+                    );
 
                 if (message) {
                     context.report({
index 9e2811809f68e788526de04076cc2df9ea744e72..2d0c61842209ae2041c09ef65941624d0c9cafe5 100644 (file)
@@ -105,9 +105,9 @@ module.exports = {
         }
 
         /**
-         * Converts an integer to to an object containing the the integer's coefficient and order of magnitude
+         * Converts an integer to to an object containing the integer's coefficient and order of magnitude
          * @param {string} stringInteger the string representation of the integer being converted
-         * @returns {Object} the object containing the the integer's coefficient and order of magnitude
+         * @returns {Object} the object containing the integer's coefficient and order of magnitude
          */
         function normalizeInteger(stringInteger) {
             const significantDigits = removeTrailingZeros(removeLeadingZeros(stringInteger));
@@ -120,9 +120,9 @@ module.exports = {
 
         /**
          *
-         * Converts a float to to an object containing the the floats's coefficient and order of magnitude
+         * Converts a float to to an object containing the floats's coefficient and order of magnitude
          * @param {string} stringFloat the string representation of the float being converted
-         * @returns {Object} the object containing the the integer's coefficient and order of magnitude
+         * @returns {Object} the object containing the integer's coefficient and order of magnitude
          */
         function normalizeFloat(stringFloat) {
             const trimmedFloat = removeLeadingZeros(stringFloat);
index f0290e93ece8db84babb31cbed8ccfa1e99eb755..15eb20bed2a49ecd826313169b0b203d04b0fcea 100644 (file)
@@ -161,17 +161,6 @@ module.exports = {
             );
         }
 
-        /**
-         * Checks whether the operator of a given node is mixed with a
-         * conditional expression.
-         * @param {ASTNode} node A node to check. This is a conditional
-         *      expression node
-         * @returns {boolean} `true` if the node was mixed.
-         */
-        function isMixedWithConditionalParent(node) {
-            return !astUtils.isParenthesised(sourceCode, node) && !astUtils.isParenthesised(sourceCode, node.test);
-        }
-
         /**
          * Gets the operator token of a given node.
          * @param {ASTNode} node A node to check. This is a BinaryExpression
@@ -220,19 +209,13 @@ module.exports = {
          * @returns {void}
          */
         function check(node) {
-            if (TARGET_NODE_TYPE.test(node.parent.type)) {
-                if (node.parent.type === "ConditionalExpression" && !shouldIgnore(node) && isMixedWithConditionalParent(node.parent)) {
-                    reportBothOperators(node);
-                } else {
-                    if (TARGET_NODE_TYPE.test(node.parent.type) &&
-                        isMixedWithParent(node) &&
-                        !shouldIgnore(node)
-                    ) {
-                        reportBothOperators(node);
-                    }
-                }
+            if (
+                TARGET_NODE_TYPE.test(node.parent.type) &&
+                isMixedWithParent(node) &&
+                !shouldIgnore(node)
+            ) {
+                reportBothOperators(node);
             }
-
         }
 
         return {
index ab6430c19ef07071dc9f413cccbc9d418292d2f2..d2606a1502a2499bc085a6379fc85afd00c6bfab 100644 (file)
@@ -21,7 +21,16 @@ module.exports = {
             url: "https://eslint.org/docs/rules/no-multi-assign"
         },
 
-        schema: [],
+        schema: [{
+            type: "object",
+            properties: {
+                ignoreNonDeclaration: {
+                    type: "boolean",
+                    default: false
+                }
+            },
+            additionalProperties: false
+        }],
 
         messages: {
             unexpectedChain: "Unexpected chained assignment."
@@ -33,10 +42,14 @@ module.exports = {
         //--------------------------------------------------------------------------
         // Public
         //--------------------------------------------------------------------------
+        const options = context.options[0] || {
+            ignoreNonDeclaration: false
+        };
+        const targetParent = options.ignoreNonDeclaration ? ["VariableDeclarator"] : ["AssignmentExpression", "VariableDeclarator"];
 
         return {
             AssignmentExpression(node) {
-                if (["AssignmentExpression", "VariableDeclarator"].indexOf(node.parent.type) !== -1) {
+                if (targetParent.indexOf(node.parent.type) !== -1) {
                     context.report({
                         node,
                         messageId: "unexpectedChain"
index c205dad8bdb79d83b18e832beeaa713c7f82f808..414164d29f79cc178b5744a377d0f40cfc379687 100644 (file)
 
 const ignore = require("ignore");
 
-const arrayOfStrings = {
-    type: "array",
-    items: { type: "string" },
-    uniqueItems: true
-};
-
 const arrayOfStringsOrObjects = {
     type: "array",
     items: {
@@ -44,6 +38,41 @@ const arrayOfStringsOrObjects = {
     uniqueItems: true
 };
 
+const arrayOfStringsOrObjectPatterns = {
+    anyOf: [
+        {
+            type: "array",
+            items: {
+                type: "string"
+            },
+            uniqueItems: true
+        },
+        {
+            type: "array",
+            items: {
+                type: "object",
+                properties: {
+                    group: {
+                        type: "array",
+                        items: {
+                            type: "string"
+                        },
+                        minItems: 1,
+                        uniqueItems: true
+                    },
+                    message: {
+                        type: "string",
+                        minLength: 1
+                    }
+                },
+                additionalProperties: false,
+                required: ["group"]
+            },
+            uniqueItems: true
+        }
+    ]
+};
+
 module.exports = {
     meta: {
         type: "suggestion",
@@ -61,6 +90,8 @@ module.exports = {
             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
+            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
@@ -80,7 +111,7 @@ module.exports = {
                         type: "object",
                         properties: {
                             paths: arrayOfStringsOrObjects,
-                            patterns: arrayOfStrings
+                            patterns: arrayOfStringsOrObjectPatterns
                         },
                         additionalProperties: false
                     }],
@@ -98,13 +129,6 @@ module.exports = {
             (Object.prototype.hasOwnProperty.call(options[0], "paths") || Object.prototype.hasOwnProperty.call(options[0], "patterns"));
 
         const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || [];
-        const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
-
-        // if no imports are restricted we don"t need to check
-        if (Object.keys(restrictedPaths).length === 0 && restrictedPatterns.length === 0) {
-            return {};
-        }
-
         const restrictedPathMessages = restrictedPaths.reduce((memo, importSource) => {
             if (typeof importSource === "string") {
                 memo[importSource] = { message: null };
@@ -117,7 +141,16 @@ module.exports = {
             return memo;
         }, {});
 
-        const restrictedPatternsMatcher = ignore().add(restrictedPatterns);
+        // Handle patterns too, either as strings or groups
+        const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
+        const restrictedPatternGroups = restrictedPatterns.length > 0 && typeof restrictedPatterns[0] === "string"
+            ? [{ 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 (Object.keys(restrictedPaths).length === 0 && restrictedPatternGroups.length === 0) {
+            return {};
+        }
 
         /**
          * Report a restricted path.
@@ -184,17 +217,19 @@ module.exports = {
         /**
          * Report a restricted path specifically for patterns.
          * @param {node} node representing the restricted path reference
+         * @param {Object} group contains a Ignore instance for paths, and the customMessage to show if it fails
          * @returns {void}
          * @private
          */
-        function reportPathForPatterns(node) {
+        function reportPathForPatterns(node, group) {
             const importSource = node.source.value.trim();
 
             context.report({
                 node,
-                messageId: "patterns",
+                messageId: group.customMessage ? "patternWithCustomMessage" : "patterns",
                 data: {
-                    importSource
+                    importSource,
+                    customMessage: group.customMessage
                 }
             });
         }
@@ -202,11 +237,12 @@ module.exports = {
         /**
          * Check if the given importSource is restricted by a pattern.
          * @param {string} importSource path of the import
+         * @param {Object} group contains a Ignore instance for paths, and the customMessage to show if it fails
          * @returns {boolean} whether the variable is a restricted pattern or not
          * @private
          */
-        function isRestrictedPattern(importSource) {
-            return restrictedPatterns.length > 0 && restrictedPatternsMatcher.ignores(importSource);
+        function isRestrictedPattern(importSource, group) {
+            return group.matcher.ignores(importSource);
         }
 
         /**
@@ -249,10 +285,11 @@ module.exports = {
             }
 
             checkRestrictedPathAndReport(importSource, importNames, node);
-
-            if (isRestrictedPattern(importSource)) {
-                reportPathForPatterns(node);
-            }
+            restrictedPatternGroups.forEach(group => {
+                if (isRestrictedPattern(importSource, group)) {
+                    reportPathForPatterns(node, group);
+                }
+            });
         }
 
         return {
index d67635d117549eae28b0f669a9a29c5797e901d7..fe516975fbc942c37bbb51620bfb1ebf377d232b 100644 (file)
 
 const astUtils = require("./utils/ast-utils");
 
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const DEFAULT_OPTIONS = {
+    allowInParentheses: true
+};
+
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
@@ -26,7 +34,15 @@ module.exports = {
             url: "https://eslint.org/docs/rules/no-sequences"
         },
 
-        schema: [],
+        schema: [{
+            properties: {
+                allowInParentheses: {
+                    type: "boolean",
+                    default: true
+                }
+            },
+            additionalProperties: false
+        }],
 
         messages: {
             unexpectedCommaExpression: "Unexpected use of comma operator."
@@ -34,6 +50,7 @@ module.exports = {
     },
 
     create(context) {
+        const options = Object.assign({}, DEFAULT_OPTIONS, context.options[0]);
         const sourceCode = context.getSourceCode();
 
         /**
@@ -99,13 +116,15 @@ module.exports = {
                 }
 
                 // Wrapping a sequence in extra parens indicates intent
-                if (requiresExtraParens(node)) {
-                    if (isParenthesisedTwice(node)) {
-                        return;
-                    }
-                } else {
-                    if (isParenthesised(node)) {
-                        return;
+                if (options.allowInParentheses) {
+                    if (requiresExtraParens(node)) {
+                        if (isParenthesisedTwice(node)) {
+                            return;
+                        }
+                    } else {
+                        if (isParenthesised(node)) {
+                            return;
+                        }
                     }
                 }
 
index 1be8590e47abcb34ccb502a6eaca1841811e22fc..a0b1db50c0b5f24c3279a476ba6295aaf7053c8f 100644 (file)
@@ -44,7 +44,8 @@ module.exports = {
         ],
 
         messages: {
-            noShadow: "'{{name}}' is already declared in the upper scope."
+            noShadow: "'{{name}}' is already declared in the upper scope on line {{shadowedLine}} column {{shadowedColumn}}.",
+            noShadowGlobal: "'{{name}}' is already a global variable."
         }
     },
 
@@ -117,6 +118,29 @@ module.exports = {
             return def && def.name.range;
         }
 
+        /**
+         * Get declared line and column of a variable.
+         * @param {eslint-scope.Variable} variable The variable to get.
+         * @returns {Object} The declared line and column of the variable.
+         */
+        function getDeclaredLocation(variable) {
+            const identifier = variable.identifiers[0];
+            let obj;
+
+            if (identifier) {
+                obj = {
+                    global: false,
+                    line: identifier.loc.start.line,
+                    column: identifier.loc.start.column + 1
+                };
+            } else {
+                obj = {
+                    global: true
+                };
+            }
+            return obj;
+        }
+
         /**
          * Checks if a variable is in TDZ of scopeVar.
          * @param {Object} variable The variable to check.
@@ -165,10 +189,18 @@ module.exports = {
                     !isOnInitializer(variable, shadowed) &&
                     !(options.hoist !== "all" && isInTdz(variable, shadowed))
                 ) {
+                    const location = getDeclaredLocation(shadowed);
+                    const messageId = location.global ? "noShadowGlobal" : "noShadow";
+                    const data = { name: variable.name };
+
+                    if (!location.global) {
+                        data.shadowedLine = location.line;
+                        data.shadowedColumn = location.column;
+                    }
                     context.report({
                         node: variable.identifiers[0],
-                        messageId: "noShadow",
-                        data: variable
+                        messageId,
+                        data
                     });
                 }
             }
index 882a0fd1c11276c9baa3769344ea5f117e9af358..58c9b33418794b286b9c150f944942b21264a40c 100644 (file)
@@ -50,6 +50,10 @@ module.exports = {
                     allowTaggedTemplates: {
                         type: "boolean",
                         default: false
+                    },
+                    enforceForJSX: {
+                        type: "boolean",
+                        default: false
                     }
                 },
                 additionalProperties: false
@@ -65,7 +69,8 @@ module.exports = {
         const config = context.options[0] || {},
             allowShortCircuit = config.allowShortCircuit || false,
             allowTernary = config.allowTernary || false,
-            allowTaggedTemplates = config.allowTaggedTemplates || false;
+            allowTaggedTemplates = config.allowTaggedTemplates || false,
+            enforceForJSX = config.enforceForJSX || false;
 
         // eslint-disable-next-line jsdoc/require-description
         /**
@@ -140,6 +145,12 @@ module.exports = {
             },
             FunctionExpression: alwaysTrue,
             Identifier: alwaysTrue,
+            JSXElement() {
+                return enforceForJSX;
+            },
+            JSXFragment() {
+                return enforceForJSX;
+            },
             Literal: alwaysTrue,
             LogicalExpression(node) {
                 if (allowShortCircuit) {
index 4dc6dc2bab34bc074264772921694a6c0572c370..f04818f8e9d5979f2879af8954dfd6207df254ab 100644 (file)
@@ -196,6 +196,17 @@ module.exports = {
 
         }
 
+        /**
+         * Checks whether a node is a sibling of the rest property or not.
+         * @param {ASTNode} node a node to check
+         * @returns {boolean} True if the node is a sibling of the rest property, otherwise false.
+         */
+        function hasRestSibling(node) {
+            return node.type === "Property" &&
+                node.parent.type === "ObjectPattern" &&
+                REST_PROPERTY_TYPE.test(node.parent.properties[node.parent.properties.length - 1].type);
+        }
+
         /**
          * Determines if a variable has a sibling rest property
          * @param {Variable} variable eslint-scope variable object.
@@ -204,16 +215,10 @@ module.exports = {
          */
         function hasRestSpreadSibling(variable) {
             if (config.ignoreRestSiblings) {
-                return variable.defs.some(def => {
-                    const propertyNode = def.name.parent;
-                    const patternNode = propertyNode.parent;
-
-                    return (
-                        propertyNode.type === "Property" &&
-                        patternNode.type === "ObjectPattern" &&
-                        REST_PROPERTY_TYPE.test(patternNode.properties[patternNode.properties.length - 1].type)
-                    );
-                });
+                const hasRestSiblingDefinition = variable.defs.some(def => hasRestSibling(def.name.parent));
+                const hasRestSiblingReference = variable.references.some(ref => hasRestSibling(ref.identifier.parent));
+
+                return hasRestSiblingDefinition || hasRestSiblingReference;
             }
 
             return false;
@@ -405,6 +410,31 @@ module.exports = {
             );
         }
 
+        /**
+         * 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.
@@ -415,23 +445,28 @@ module.exports = {
         function isReadForItself(ref, rhsNode) {
             const id = ref.identifier;
             const parent = id.parent;
-            const grandparent = parent.parent;
 
             return ref.isRead() && (
 
                 // self update. e.g. `a += 1`, `a++`
-                (// in RHS of an assignment for itself. e.g. `a = a + 1`
-                    ((
+                (
+                    (
                         parent.type === "AssignmentExpression" &&
-                    grandparent.type === "ExpressionStatement" &&
-                    parent.left === id
+                        parent.left === id &&
+                        isUnusedExpression(parent)
                     ) ||
+                    (
+                        parent.type === "UpdateExpression" &&
+                        isUnusedExpression(parent)
+                    )
+                ) ||
+
+                // in RHS of an assignment for itself. e.g. `a = a + 1`
                 (
-                    parent.type === "UpdateExpression" &&
-                    grandparent.type === "ExpressionStatement"
-                ) || rhsNode &&
-                isInside(id, rhsNode) &&
-                !isInsideOfStorableFunction(id, rhsNode)))
+                    rhsNode &&
+                    isInside(id, rhsNode) &&
+                    !isInsideOfStorableFunction(id, rhsNode)
+                )
             );
         }
 
@@ -619,10 +654,18 @@ module.exports = {
 
                     // Report the first declaration.
                     if (unusedVar.defs.length > 0) {
+
+                        // report last write reference, https://github.com/eslint/eslint/issues/14324
+                        const writeReferences = unusedVar.references.filter(ref => ref.isWrite() && ref.from.variableScope === unusedVar.scope.variableScope);
+
+                        let referenceToReport;
+
+                        if (writeReferences.length > 0) {
+                            referenceToReport = writeReferences[writeReferences.length - 1];
+                        }
+
                         context.report({
-                            node: unusedVar.references.length ? unusedVar.references[
-                                unusedVar.references.length - 1
-                            ].identifier : unusedVar.identifiers[0],
+                            node: referenceToReport ? referenceToReport.identifier : unusedVar.identifiers[0],
                             messageId: "unusedVar",
                             data: unusedVar.references.some(ref => ref.isWrite())
                                 ? getAssignedMessageData(unusedVar)
index 958e3d5dd4e4d5eafc8c1af7039d7fa784b95118..529c16439e3806966e5210da4af92feee3d70bdc 100644 (file)
@@ -11,7 +11,6 @@
 
 const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("eslint-utils");
 const { RegExpParser, visitRegExpAST } = require("regexpp");
-const lodash = require("lodash");
 
 //------------------------------------------------------------------------------
 // Helpers
@@ -137,7 +136,7 @@ module.exports = {
 
                             // the opposite of the previous when the regex is matching backward in a lookbehind context.
                             messageId = "backward";
-                        } else if (lodash.last(groupCut).type === "Alternative") {
+                        } else if (groupCut[groupCut.length - 1].type === "Alternative") {
 
                             // group's and bref's ancestor nodes below the lowest common ancestor are sibling alternatives => they're disjunctive.
                             messageId = "disjunctive";
index e0505a318efaae74b3d048ee54f9de46ef8713b9..a1cacc296129feae1fb38fe6ddc0d33835e61056 100644 (file)
@@ -8,7 +8,6 @@
 // Requirements
 //------------------------------------------------------------------------------
 
-const lodash = require("lodash");
 const astUtils = require("./utils/ast-utils");
 
 //------------------------------------------------------------------------------
@@ -95,9 +94,16 @@ module.exports = {
             }
         }
 
+        /**
+         * A no-op function to act as placeholder for checking a node when the `enforceForClassMembers` option is `false`.
+         * @returns {void}
+         * @private
+         */
+        function noop() {}
+
         return {
             Property: check,
-            MethodDefinition: enforceForClassMembers ? check : lodash.noop
+            MethodDefinition: enforceForClassMembers ? check : noop
         };
     }
 };
index fa88f37f50b79c108784b989d25224c043d00d62..a7cec025da7e701378eff29e05f1f7ec91b4f067 100644 (file)
@@ -5,6 +5,12 @@
 
 "use strict";
 
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
@@ -54,11 +60,10 @@ module.exports = {
          * Reports error for unnecessarily renamed assignments
          * @param {ASTNode} node node to report
          * @param {ASTNode} initial node with initial name value
-         * @param {ASTNode} result node with new name value
          * @param {string} type the type of the offending node
          * @returns {void}
          */
-        function reportError(node, initial, result, type) {
+        function reportError(node, initial, type) {
             const name = initial.type === "Identifier" ? initial.name : initial.value;
 
             return context.report({
@@ -69,18 +74,21 @@ module.exports = {
                     type
                 },
                 fix(fixer) {
-                    if (sourceCode.commentsExistBetween(initial, result)) {
+                    const replacementNode = node.type === "Property" ? node.value : node.local;
+
+                    if (sourceCode.getCommentsInside(node).length > sourceCode.getCommentsInside(replacementNode).length) {
                         return null;
                     }
 
-                    const replacementText = result.type === "AssignmentPattern"
-                        ? sourceCode.getText(result)
-                        : name;
+                    // Don't autofix code such as `({foo: (foo) = a} = obj);`, parens are not allowed in shorthand properties.
+                    if (
+                        replacementNode.type === "AssignmentPattern" &&
+                        astUtils.isParenthesised(sourceCode, replacementNode.left)
+                    ) {
+                        return null;
+                    }
 
-                    return fixer.replaceTextRange([
-                        initial.range[0],
-                        result.range[1]
-                    ], replacementText);
+                    return fixer.replaceText(node, sourceCode.getText(replacementNode));
                 }
             });
         }
@@ -97,19 +105,11 @@ module.exports = {
 
             for (const property of node.properties) {
 
-                /*
-                 * TODO: Remove after babel-eslint removes ExperimentalRestProperty
-                 * https://github.com/eslint/eslint/issues/12335
-                 */
-                if (property.type === "ExperimentalRestProperty") {
-                    continue;
-                }
-
                 /**
                  * Properties using shorthand syntax and rest elements can not be renamed.
                  * If the property is computed, we have no idea if a rename is useless or not.
                  */
-                if (property.shorthand || property.type === "RestElement" || property.computed) {
+                if (property.type !== "Property" || property.shorthand || property.computed) {
                     continue;
                 }
 
@@ -117,7 +117,7 @@ module.exports = {
                 const renamedKey = property.value.type === "AssignmentPattern" ? property.value.left.name : property.value.name;
 
                 if (key === renamedKey) {
-                    reportError(property, property.key, property.value, "Destructuring assignment");
+                    reportError(property, property.key, "Destructuring assignment");
                 }
             }
         }
@@ -134,7 +134,7 @@ module.exports = {
 
             if (node.imported.name === node.local.name &&
                     node.imported.range[0] !== node.local.range[0]) {
-                reportError(node, node.imported, node.local, "Import");
+                reportError(node, node.imported, "Import");
             }
         }
 
@@ -150,7 +150,7 @@ module.exports = {
 
             if (node.local.name === node.exported.name &&
                     node.local.range[0] !== node.exported.range[0]) {
-                reportError(node, node.local, node.exported, "Export");
+                reportError(node, node.local, "Export");
             }
 
         }
index 0691a31f77e429bcd6ce69a6f9995b452a873b39..e5f702bc7d710e652ab3e1da91bcb545e562622b 100644 (file)
@@ -5,7 +5,7 @@
 
 "use strict";
 
-const { escapeRegExp } = require("lodash");
+const escapeRegExp = require("escape-string-regexp");
 const astUtils = require("./utils/ast-utils");
 
 const CHAR_LIMIT = 40;
index 616d59851d646d5085bbc77a58a61101dced609c..1fbea00c5d7e403db78c1f25b1c43dc93ec3052f 100644 (file)
@@ -10,7 +10,6 @@
 //------------------------------------------------------------------------------
 
 const astUtils = require("./utils/ast-utils");
-const lodash = require("lodash");
 
 //------------------------------------------------------------------------------
 // Helpers
@@ -69,6 +68,24 @@ function normalizeOptionValue(value) {
     return { multiline, minProperties, consistent };
 }
 
+/**
+ * Checks if a value is an object.
+ * @param {any} value The value to check
+ * @returns {boolean} `true` if the value is an object, otherwise `false`
+ */
+function isObject(value) {
+    return typeof value === "object" && value !== null;
+}
+
+/**
+ * Checks if an option is a node-specific option
+ * @param {any} option The option to check
+ * @returns {boolean} `true` if the option is node-specific, otherwise `false`
+ */
+function isNodeSpecificOption(option) {
+    return isObject(option) || typeof option === "string";
+}
+
 /**
  * Normalizes a given option value.
  * @param {string|Object|undefined} options An option value to parse.
@@ -80,9 +97,7 @@ function normalizeOptionValue(value) {
  * }} Normalized option object.
  */
 function normalizeOptions(options) {
-    const isNodeSpecificOption = lodash.overSome([lodash.isPlainObject, lodash.isString]);
-
-    if (lodash.isPlainObject(options) && lodash.some(options, isNodeSpecificOption)) {
+    if (isObject(options) && Object.values(options).some(isNodeSpecificOption)) {
         return {
             ObjectExpression: normalizeOptionValue(options.ObjectExpression),
             ObjectPattern: normalizeOptionValue(options.ObjectPattern),
@@ -134,7 +149,7 @@ module.exports = {
         type: "layout",
 
         docs: {
-            description: "enforce consistent line breaks inside braces",
+            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"
index 439a4db3c963f6be6f11a3d41d7d92b04d58022d..f6e79e71c3e9758ba292b64c1d57543df6e1fa8e 100644 (file)
@@ -5,6 +5,11 @@
 
 "use strict";
 
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const FixTracker = require("./utils/fix-tracker");
 const astUtils = require("./utils/ast-utils");
 
 //------------------------------------------------------------------------------
@@ -451,10 +456,18 @@ module.exports = {
                         messageId: "useConst",
                         data: node,
                         fix: shouldFix
-                            ? fixer => fixer.replaceText(
-                                sourceCode.getFirstToken(varDeclParent, t => t.value === varDeclParent.kind),
-                                "const"
-                            )
+                            ? fixer => {
+                                const letKeywordToken = sourceCode.getFirstToken(varDeclParent, t => t.value === varDeclParent.kind);
+
+                                /**
+                                 * Extend the replacement range to the whole declaration,
+                                 * in order to prevent other fixes in the same pass
+                                 * https://github.com/eslint/eslint/issues/13899
+                                 */
+                                return new FixTracker(fixer, sourceCode)
+                                    .retainRange(varDeclParent.range)
+                                    .replaceTextRange(letKeywordToken.range, "const");
+                            }
                             : null
                     });
                 });
index 413f7272cc15f2a07e8e48e42bccdefb003f0ace..f82bb75c1225d3c771d0095696177b35d535569f 100644 (file)
@@ -168,7 +168,7 @@ module.exports = {
          * Assignment expression is not fixed.
          * Array destructuring is not fixed.
          * Renamed property is not fixed.
-         * @param {ASTNode} node the the node to evaluate
+         * @param {ASTNode} node the node to evaluate
          * @returns {boolean} whether or not the node should be fixed
          */
         function shouldFix(node) {
index e322566238809b3b403475d1669b76b8bf598da3..d1b67e5e280b93193e3d23e78c834f0620a38e71 100644 (file)
@@ -82,7 +82,8 @@ module.exports = {
             description: "enforce the consistent use of the radix argument when using `parseInt()`",
             category: "Best Practices",
             recommended: false,
-            url: "https://eslint.org/docs/rules/radix"
+            url: "https://eslint.org/docs/rules/radix",
+            suggestion: true
         },
 
         schema: [
@@ -95,7 +96,8 @@ module.exports = {
             missingParameters: "Missing parameters.",
             redundantRadix: "Redundant radix parameter.",
             missingRadix: "Missing radix parameter.",
-            invalidRadix: "Invalid radix parameter, must be an integer between 2 and 36."
+            invalidRadix: "Invalid radix parameter, must be an integer between 2 and 36.",
+            addRadixParameter10: "Add radix parameter `10` for parsing decimal numbers."
         }
     },
 
@@ -123,7 +125,21 @@ module.exports = {
                     if (mode === MODE_ALWAYS) {
                         context.report({
                             node,
-                            messageId: "missingRadix"
+                            messageId: "missingRadix",
+                            suggest: [
+                                {
+                                    messageId: "addRadixParameter10",
+                                    fix(fixer) {
+                                        const sourceCode = context.getSourceCode();
+                                        const tokens = sourceCode.getTokens(node);
+                                        const lastToken = tokens[tokens.length - 1]; // Parenthesis.
+                                        const secondToLastToken = tokens[tokens.length - 2]; // May or may not be a comma.
+                                        const hasTrailingComma = secondToLastToken.type === "Punctuator" && secondToLastToken.value === ",";
+
+                                        return fixer.insertTextBefore(lastToken, hasTrailingComma ? " 10," : ", 10");
+                                    }
+                                }
+                            ]
                         });
                     }
                     break;
index c552f1bd8258b8e67bac4900c8b9756266b6156d..b3df907420c0409c3b8752d15e3e94b09ad369ff 100644 (file)
  */
 function createReferenceMap(scope, outReferenceMap = new Map()) {
     for (const reference of scope.references) {
+        if (reference.resolved === null) {
+            continue;
+        }
+
         outReferenceMap.set(reference.identifier, reference);
     }
     for (const childScope of scope.childScopes) {
@@ -86,42 +90,42 @@ class SegmentInfo {
      * @returns {void}
      */
     initialize(segment) {
-        const outdatedReadVariableNames = new Set();
-        const freshReadVariableNames = new Set();
+        const outdatedReadVariables = new Set();
+        const freshReadVariables = new Set();
 
         for (const prevSegment of segment.prevSegments) {
             const info = this.info.get(prevSegment);
 
             if (info) {
-                info.outdatedReadVariableNames.forEach(Set.prototype.add, outdatedReadVariableNames);
-                info.freshReadVariableNames.forEach(Set.prototype.add, freshReadVariableNames);
+                info.outdatedReadVariables.forEach(Set.prototype.add, outdatedReadVariables);
+                info.freshReadVariables.forEach(Set.prototype.add, freshReadVariables);
             }
         }
 
-        this.info.set(segment, { outdatedReadVariableNames, freshReadVariableNames });
+        this.info.set(segment, { outdatedReadVariables, freshReadVariables });
     }
 
     /**
      * Mark a given variable as read on given segments.
      * @param {PathSegment[]} segments The segments that it read the variable on.
-     * @param {string} variableName The variable name to be read.
+     * @param {Variable} variable The variable to be read.
      * @returns {void}
      */
-    markAsRead(segments, variableName) {
+    markAsRead(segments, variable) {
         for (const segment of segments) {
             const info = this.info.get(segment);
 
             if (info) {
-                info.freshReadVariableNames.add(variableName);
+                info.freshReadVariables.add(variable);
 
                 // If a variable is freshly read again, then it's no more out-dated.
-                info.outdatedReadVariableNames.delete(variableName);
+                info.outdatedReadVariables.delete(variable);
             }
         }
     }
 
     /**
-     * Move `freshReadVariableNames` to `outdatedReadVariableNames`.
+     * Move `freshReadVariables` to `outdatedReadVariables`.
      * @param {PathSegment[]} segments The segments to process.
      * @returns {void}
      */
@@ -130,8 +134,8 @@ class SegmentInfo {
             const info = this.info.get(segment);
 
             if (info) {
-                info.freshReadVariableNames.forEach(Set.prototype.add, info.outdatedReadVariableNames);
-                info.freshReadVariableNames.clear();
+                info.freshReadVariables.forEach(Set.prototype.add, info.outdatedReadVariables);
+                info.freshReadVariables.clear();
             }
         }
     }
@@ -139,14 +143,14 @@ class SegmentInfo {
     /**
      * Check if a given variable is outdated on the current segments.
      * @param {PathSegment[]} segments The current segments.
-     * @param {string} variableName The variable name to check.
+     * @param {Variable} variable The variable to check.
      * @returns {boolean} `true` if the variable is outdated on the segments.
      */
-    isOutdated(segments, variableName) {
+    isOutdated(segments, variable) {
         for (const segment of segments) {
             const info = this.info.get(segment);
 
-            if (info && info.outdatedReadVariableNames.has(variableName)) {
+            if (info && info.outdatedReadVariables.has(variable)) {
                 return true;
             }
         }
@@ -214,14 +218,13 @@ module.exports = {
                 if (!reference) {
                     return;
                 }
-                const name = reference.identifier.name;
                 const variable = reference.resolved;
                 const writeExpr = getWriteExpr(reference);
                 const isMemberAccess = reference.identifier.parent.type === "MemberExpression";
 
                 // Add a fresh read variable.
                 if (reference.isRead() && !(writeExpr && writeExpr.parent.operator === "=")) {
-                    segmentInfo.markAsRead(codePath.currentSegments, name);
+                    segmentInfo.markAsRead(codePath.currentSegments, variable);
                 }
 
                 /*
@@ -245,7 +248,7 @@ module.exports = {
 
             /*
              * Verify assignments.
-             * If the reference exists in `outdatedReadVariableNames` list, report it.
+             * If the reference exists in `outdatedReadVariables` list, report it.
              */
             ":expression:exit"(node) {
                 const { codePath, referenceMap } = stack;
@@ -267,9 +270,9 @@ module.exports = {
                     assignmentReferences.delete(node);
 
                     for (const reference of references) {
-                        const name = reference.identifier.name;
+                        const variable = reference.resolved;
 
-                        if (segmentInfo.isOutdated(codePath.currentSegments, name)) {
+                        if (segmentInfo.isOutdated(codePath.currentSegments, variable)) {
                             context.report({
                                 node: node.parent,
                                 messageId: "nonAtomicUpdate",
index 471c22210eb613cfa7d655f9fd862189fb0776f8..3c550984fc625295559095c25ebd86b5d08646b8 100644 (file)
@@ -132,7 +132,9 @@ module.exports = {
 
             if (nonSpacedConsequentNode) {
                 report(node, nonSpacedConsequentNode);
-            } else if (nonSpacedAlternateNode) {
+            }
+
+            if (nonSpacedAlternateNode) {
                 report(node, nonSpacedAlternateNode);
             }
         }
index d3221f0ea798ac62708d73a8f658270d3736ab63..226a2d44798a192b87920a26ed6e8c76186140e2 100644 (file)
@@ -4,7 +4,7 @@
  */
 "use strict";
 
-const lodash = require("lodash");
+const escapeRegExp = require("escape-string-regexp");
 const astUtils = require("./utils/ast-utils");
 
 //------------------------------------------------------------------------------
@@ -17,7 +17,7 @@ const astUtils = require("./utils/ast-utils");
  * @returns {string} An escaped string.
  */
 function escape(s) {
-    return `(?:${lodash.escapeRegExp(s)})`;
+    return `(?:${escapeRegExp(s)})`;
 }
 
 /**
index 53ffeb7e6d153e7b57e5e2685baacbcdc7856bd7..0c7e888c976d8d19729aff7bb8e49568dfb729f9 100644 (file)
@@ -101,7 +101,7 @@ module.exports = {
         }
 
         /**
-         * Checks the the given `CallExpression` node for `.indexOf(NaN)` and `.lastIndexOf(NaN)`.
+         * Checks the given `CallExpression` node for `.indexOf(NaN)` and `.lastIndexOf(NaN)`.
          * @param {ASTNode} node The node to check.
          * @returns {void}
          */
index 679eebb4c458d7cef9591cf344b2e8b6ccbbbc81..6b853001132ed29c9a2b779d165b219e2a2442c3 100644 (file)
@@ -11,7 +11,7 @@
 
 const esutils = require("esutils");
 const espree = require("espree");
-const lodash = require("lodash");
+const escapeRegExp = require("escape-string-regexp");
 const {
     breakableTypePattern,
     createGlobalLinebreakMatcher,
@@ -1756,7 +1756,7 @@ module.exports = {
      * @returns {SourceLocation} The `loc` object.
      */
     getNameLocationInGlobalDirectiveComment(sourceCode, comment, name) {
-        const namePattern = new RegExp(`[\\s,]${lodash.escapeRegExp(name)}(?:$|[\\s,:])`, "gu");
+        const namePattern = new RegExp(`[\\s,]${escapeRegExp(name)}(?:$|[\\s,:])`, "gu");
 
         // To ignore the first text "global".
         namePattern.lastIndex = comment.value.indexOf("global") + 6;
index 1438eaa69bff86c383d9f4f413050132fcbc7cac..1a0501ab057a1123c3ae956b7372bf2771bd7679 100644 (file)
@@ -9,7 +9,6 @@
 //------------------------------------------------------------------------------
 
 const path = require("path");
-const lodash = require("lodash");
 
 //------------------------------------------------------------------------------
 // Private
@@ -28,6 +27,8 @@ const deprecationWarningMessages = {
         "projects in order to avoid loading '~/.eslintrc.*' accidentally."
 };
 
+const sourceFileErrorCache = new Set();
+
 /**
  * Emits a deprecation warning containing a given filepath. A new deprecation warning is emitted
  * for each unique file path, but repeated invocations with the same file path have no effect.
@@ -36,7 +37,15 @@ const deprecationWarningMessages = {
  * @param {string} errorCode The warning message to show.
  * @returns {void}
  */
-const emitDeprecationWarning = lodash.memoize((source, errorCode) => {
+function emitDeprecationWarning(source, errorCode) {
+    const cacheKey = JSON.stringify({ source, errorCode });
+
+    if (sourceFileErrorCache.has(cacheKey)) {
+        return;
+    }
+
+    sourceFileErrorCache.add(cacheKey);
+
     const rel = path.relative(process.cwd(), source);
     const message = deprecationWarningMessages[errorCode];
 
@@ -45,7 +54,7 @@ const emitDeprecationWarning = lodash.memoize((source, errorCode) => {
         "DeprecationWarning",
         errorCode
     );
-}, (...args) => JSON.stringify(args));
+}
 
 //------------------------------------------------------------------------------
 // Public Interface
index feed005330e622cbdeb1b0dc0b744009fac68f6c..aa5eff756a8cd02188bf481f6ca022c017858a51 100644 (file)
@@ -11,7 +11,7 @@
 
 const path = require("path");
 const spawn = require("cross-spawn");
-const { isEmpty } = require("lodash");
+const os = require("os");
 const log = require("../shared/logging");
 const packageJson = require("../../package.json");
 
@@ -107,7 +107,7 @@ function environment() {
              * Checking globally returns an empty JSON object, while local checks
              * include the name and version of the local project.
              */
-            if (isEmpty(parsedStdout) || !(parsedStdout.dependencies && parsedStdout.dependencies.eslint)) {
+            if (Object.keys(parsedStdout).length === 0 || !(parsedStdout.dependencies && parsedStdout.dependencies.eslint)) {
                 return "Not found";
             }
 
@@ -141,7 +141,8 @@ function environment() {
         `Node version: ${getBinVersion("node")}`,
         `npm version: ${getBinVersion("npm")}`,
         `Local ESLint version: ${getNpmPackageVersion("eslint", { global: false })}`,
-        `Global ESLint version: ${getNpmPackageVersion("eslint", { global: true })}`
+        `Global ESLint version: ${getNpmPackageVersion("eslint", { global: true })}`,
+        `Operating System: ${os.platform()} ${os.release()}`
     ].join("\n");
 }
 
diff --git a/eslint/lib/shared/string-utils.js b/eslint/lib/shared/string-utils.js
new file mode 100644 (file)
index 0000000..e4a55d7
--- /dev/null
@@ -0,0 +1,22 @@
+/**
+ * @fileoverview Utilities to operate on strings.
+ * @author Stephen Wade
+ */
+
+"use strict";
+
+/**
+ * Converts the first letter of a string to uppercase.
+ * @param {string} string The string to operate on
+ * @returns {string} The converted string
+ */
+function upperCaseFirst(string) {
+    if (string.length <= 1) {
+        return string.toUpperCase();
+    }
+    return string[0].toUpperCase() + string.slice(1);
+}
+
+module.exports = {
+    upperCaseFirst
+};
index 6b20495b6fc807a07b82cc9c4325c9f5f78203fd..c13ce29b877a4aa0f83647c5d799408946aaf2c4 100644 (file)
@@ -12,8 +12,7 @@ const
     { isCommentToken } = require("eslint-utils"),
     TokenStore = require("./token-store"),
     astUtils = require("../shared/ast-utils"),
-    Traverser = require("../shared/traverser"),
-    lodash = require("lodash");
+    Traverser = require("../shared/traverser");
 
 //------------------------------------------------------------------------------
 // Private
@@ -531,10 +530,12 @@ class SourceCode extends TokenStore {
         }
 
         /*
-         * To figure out which line rangeIndex is on, determine the last index at which rangeIndex could
-         * be inserted into lineIndices to keep the list sorted.
+         * To figure out which line index is on, determine the last place at which index could
+         * be inserted into lineStartIndices to keep the list sorted.
          */
-        const lineNumber = lodash.sortedLastIndex(this.lineStartIndices, index);
+        const lineNumber = index >= this.lineStartIndices[this.lineStartIndices.length - 1]
+            ? this.lineStartIndices.length
+            : this.lineStartIndices.findIndex(el => index < el);
 
         return { line: lineNumber, column: index - this.lineStartIndices[lineNumber - 1] };
     }
index 21e1d6ff7c3b6d1504fdc7f03798738d56099acf..a2bd77de71a32ecf8166e76e704ed6ee04a05865 100644 (file)
@@ -4,12 +4,6 @@
  */
 "use strict";
 
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const lodash = require("lodash");
-
 //------------------------------------------------------------------------------
 // Helpers
 //------------------------------------------------------------------------------
@@ -29,18 +23,16 @@ function getStartLocation(token) {
 //------------------------------------------------------------------------------
 
 /**
- * Binary-searches the index of the first token which is after the given location.
+ * Finds the index of the first token which is after the given location.
  * If it was not found, this returns `tokens.length`.
  * @param {(Token|Comment)[]} tokens It searches the token in this list.
  * @param {number} location The location to search.
  * @returns {number} The found index or `tokens.length`.
  */
 exports.search = function search(tokens, location) {
-    return lodash.sortedIndexBy(
-        tokens,
-        { range: [location] },
-        getStartLocation
-    );
+    const index = tokens.findIndex(el => location <= getStartLocation(el));
+
+    return index === -1 ? tokens.length : index;
 };
 
 /**
diff --git a/eslint/messages/all-files-ignored.js b/eslint/messages/all-files-ignored.js
new file mode 100644 (file)
index 0000000..d85828d
--- /dev/null
@@ -0,0 +1,16 @@
+"use strict";
+
+module.exports = function(it) {
+    const { pattern } = it;
+
+    return `
+You are linting "${pattern}", but all of the files matching the glob pattern "${pattern}" are ignored.
+
+If you don't want to lint these files, remove the pattern "${pattern}" from the list of arguments passed to ESLint.
+
+If you do want to lint these files, try the following solutions:
+
+* Check your .eslintignore file, or the eslintIgnore property in package.json, to ensure that the files are not configured to be ignored.
+* Explicitly list the files from this glob that you'd like to lint on the command-line, rather than providing a glob as an argument.
+`.trimLeft();
+};
diff --git a/eslint/messages/all-files-ignored.txt b/eslint/messages/all-files-ignored.txt
deleted file mode 100644 (file)
index 3f4c8ce..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-You are linting "<%= pattern %>", but all of the files matching the glob pattern "<%= pattern %>" are ignored.
-
-If you don't want to lint these files, remove the pattern "<%= pattern %>" from the list of arguments passed to ESLint.
-
-If you do want to lint these files, try the following solutions:
-
-* Check your .eslintignore file, or the eslintIgnore property in package.json, to ensure that the files are not configured to be ignored.
-* Explicitly list the files from this glob that you'd like to lint on the command-line, rather than providing a glob as an argument.
diff --git a/eslint/messages/extend-config-missing.js b/eslint/messages/extend-config-missing.js
new file mode 100644 (file)
index 0000000..db8a5c6
--- /dev/null
@@ -0,0 +1,13 @@
+"use strict";
+
+module.exports = function(it) {
+    const { configName, importerName } = it;
+
+    return `
+ESLint couldn't find the config "${configName}" to extend from. Please check that the name of the config is correct.
+
+The config "${configName}" was referenced from the config file in "${importerName}".
+
+If you still have problems, please stop by https://eslint.org/chat/help to chat with the team.
+`.trimLeft();
+};
diff --git a/eslint/messages/extend-config-missing.txt b/eslint/messages/extend-config-missing.txt
deleted file mode 100644 (file)
index 4defd7a..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-ESLint couldn't find the config "<%- configName %>" to extend from. Please check that the name of the config is correct.
-
-The config "<%- configName %>" was referenced from the config file in "<%- importerName %>".
-
-If you still have problems, please stop by https://eslint.org/chat/help to chat with the team.
diff --git a/eslint/messages/failed-to-read-json.js b/eslint/messages/failed-to-read-json.js
new file mode 100644 (file)
index 0000000..5114de3
--- /dev/null
@@ -0,0 +1,11 @@
+"use strict";
+
+module.exports = function(it) {
+    const { path, message } = it;
+
+    return `
+Failed to read JSON file at ${path}:
+
+${message}
+`.trimLeft();
+};
diff --git a/eslint/messages/failed-to-read-json.txt b/eslint/messages/failed-to-read-json.txt
deleted file mode 100644 (file)
index b5e2b86..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Failed to read JSON file at <%= path %>:
-
-<%= message %>
diff --git a/eslint/messages/file-not-found.js b/eslint/messages/file-not-found.js
new file mode 100644 (file)
index 0000000..26a5d57
--- /dev/null
@@ -0,0 +1,10 @@
+"use strict";
+
+module.exports = function(it) {
+    const { pattern, globDisabled } = it;
+
+    return `
+No files matching the pattern "${pattern}"${globDisabled ? " (with disabling globs)" : ""} were found.
+Please check for typing mistakes in the pattern.
+`.trimLeft();
+};
diff --git a/eslint/messages/file-not-found.txt b/eslint/messages/file-not-found.txt
deleted file mode 100644 (file)
index 639498e..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-No files matching the pattern "<%= pattern %>"<% if (globDisabled) { %> (with disabling globs)<% } %> were found.
-Please check for typing mistakes in the pattern.
diff --git a/eslint/messages/no-config-found.js b/eslint/messages/no-config-found.js
new file mode 100644 (file)
index 0000000..1042143
--- /dev/null
@@ -0,0 +1,15 @@
+"use strict";
+
+module.exports = function(it) {
+    const { directoryPath } = it;
+
+    return `
+ESLint couldn't find a configuration file. To set up a configuration file for this project, please run:
+
+    eslint --init
+
+ESLint looked for configuration files in ${directoryPath} and its ancestors. If it found none, it then looked in your home directory.
+
+If you think you already have a configuration file or if you need more help, please stop by the ESLint chat room: https://eslint.org/chat/help
+`.trimLeft();
+};
diff --git a/eslint/messages/no-config-found.txt b/eslint/messages/no-config-found.txt
deleted file mode 100644 (file)
index b46a7e5..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-ESLint couldn't find a configuration file. To set up a configuration file for this project, please run:
-
-    eslint --init
-
-ESLint looked for configuration files in <%= directoryPath %> and its ancestors. If it found none, it then looked in your home directory.
-
-If you think you already have a configuration file or if you need more help, please stop by the ESLint chat room: https://eslint.org/chat/help
diff --git a/eslint/messages/plugin-conflict.js b/eslint/messages/plugin-conflict.js
new file mode 100644 (file)
index 0000000..c8c060e
--- /dev/null
@@ -0,0 +1,22 @@
+"use strict";
+
+module.exports = function(it) {
+    const { pluginId, plugins } = it;
+
+    let result = `ESLint couldn't determine the plugin "${pluginId}" uniquely.
+`;
+
+    for (const { filePath, importerName } of plugins) {
+        result += `
+- ${filePath} (loaded in "${importerName}")`;
+    }
+
+    result += `
+
+Please remove the "plugins" setting from either config or remove either plugin installation.
+
+If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
+`;
+
+    return result;
+};
diff --git a/eslint/messages/plugin-conflict.txt b/eslint/messages/plugin-conflict.txt
deleted file mode 100644 (file)
index 3ab4b34..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-ESLint couldn't determine the plugin "<%- pluginId %>" uniquely.
-<% for (const { filePath, importerName } of plugins) { %>
-- <%= filePath %> (loaded in "<%= importerName %>")<% } %>
-
-Please remove the "plugins" setting from either config or remove either plugin installation.
-
-If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
diff --git a/eslint/messages/plugin-invalid.js b/eslint/messages/plugin-invalid.js
new file mode 100644 (file)
index 0000000..7913576
--- /dev/null
@@ -0,0 +1,16 @@
+"use strict";
+
+module.exports = function(it) {
+    const { configName, importerName } = it;
+
+    return `
+"${configName}" is invalid syntax for a config specifier.
+
+* If your intention is to extend from a configuration exported from the plugin, add the configuration name after a slash: e.g. "${configName}/myConfig".
+* If this is the name of a shareable config instead of a plugin, remove the "plugin:" prefix: i.e. "${configName.slice("plugin:".length)}".
+
+"${configName}" was referenced from the config file in "${importerName}".
+
+If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
+`.trimLeft();
+};
diff --git a/eslint/messages/plugin-invalid.txt b/eslint/messages/plugin-invalid.txt
deleted file mode 100644 (file)
index 3ee2518..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-"<%- configName %>" is invalid syntax for a config specifier.
-
-* If your intention is to extend from a configuration exported from the plugin, add the configuration name after a slash: e.g. "<%- configName %>/myConfig".
-* If this is the name of a shareable config instead of a plugin, remove the "plugin:" prefix: i.e. "<%- configName.slice("plugin:".length) %>".
-
-"<%- configName %>" was referenced from the config file in "<%- importerName %>".
-
-If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
diff --git a/eslint/messages/plugin-missing.js b/eslint/messages/plugin-missing.js
new file mode 100644 (file)
index 0000000..f58c78c
--- /dev/null
@@ -0,0 +1,19 @@
+"use strict";
+
+module.exports = function(it) {
+    const { pluginName, resolvePluginsRelativeTo, importerName } = it;
+
+    return `
+ESLint couldn't find the plugin "${pluginName}".
+
+(The package "${pluginName}" was not found when loaded as a Node module from the directory "${resolvePluginsRelativeTo}".)
+
+It's likely that the plugin isn't installed correctly. Try reinstalling by running the following:
+
+    npm install ${pluginName}@latest --save-dev
+
+The plugin "${pluginName}" was referenced from the config file in "${importerName}".
+
+If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
+`.trimLeft();
+};
diff --git a/eslint/messages/plugin-missing.txt b/eslint/messages/plugin-missing.txt
deleted file mode 100644 (file)
index aa25f59..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-ESLint couldn't find the plugin "<%- pluginName %>".
-
-(The package "<%- pluginName %>" was not found when loaded as a Node module from the directory "<%- resolvePluginsRelativeTo %>".)
-
-It's likely that the plugin isn't installed correctly. Try reinstalling by running the following:
-
-    npm install <%- pluginName %>@latest --save-dev
-
-The plugin "<%- pluginName %>" was referenced from the config file in "<%- importerName %>".
-
-If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
diff --git a/eslint/messages/print-config-with-directory-path.js b/eslint/messages/print-config-with-directory-path.js
new file mode 100644 (file)
index 0000000..6a5d571
--- /dev/null
@@ -0,0 +1,8 @@
+"use strict";
+
+module.exports = function() {
+    return `
+The '--print-config' CLI option requires a path to a source code file rather than a directory.
+See also: https://eslint.org/docs/user-guide/command-line-interface#--print-config
+`.trimLeft();
+};
diff --git a/eslint/messages/print-config-with-directory-path.txt b/eslint/messages/print-config-with-directory-path.txt
deleted file mode 100644 (file)
index 1afc9b1..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-The '--print-config' CLI option requires a path to a source code file rather than a directory.
-See also: https://eslint.org/docs/user-guide/command-line-interface#--print-config
diff --git a/eslint/messages/whitespace-found.js b/eslint/messages/whitespace-found.js
new file mode 100644 (file)
index 0000000..4ce49ca
--- /dev/null
@@ -0,0 +1,11 @@
+"use strict";
+
+module.exports = function(it) {
+    const { pluginName } = it;
+
+    return `
+ESLint couldn't find the plugin "${pluginName}". because there is whitespace in the name. Please check your configuration and remove all whitespace from the plugin name.
+
+If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
+`.trimLeft();
+};
diff --git a/eslint/messages/whitespace-found.txt b/eslint/messages/whitespace-found.txt
deleted file mode 100644 (file)
index 3eed1af..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-ESLint couldn't find the plugin "<%- pluginName %>". because there is whitespace in the name. Please check your configuration and remove all whitespace from the plugin name.
-
-If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
index 297e8c5df40476dfa4a5b8e852d8288e29c49111..601033eb44e5da3f448f4db4ad2cdac1162ecfab 100644 (file)
@@ -1,6 +1,6 @@
 {
   "name": "eslint",
-  "version": "7.18.0",
+  "version": "7.28.0",
   "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
   "description": "An AST-based pattern checker for JavaScript.",
   "bin": {
     "pre-commit": "lint-staged"
   },
   "lint-staged": {
-    "*.js": [
-      "eslint --fix",
-      "git add"
-    ],
+    "*.js": "eslint --fix",
     "*.md": "markdownlint"
   },
   "files": [
   "homepage": "https://eslint.org",
   "bugs": "https://github.com/eslint/eslint/issues/",
   "dependencies": {
-    "@babel/code-frame": "^7.0.0",
-    "@eslint/eslintrc": "^0.3.0",
+    "@babel/code-frame": "7.12.11",
+    "@eslint/eslintrc": "^0.4.2",
     "ajv": "^6.10.0",
     "chalk": "^4.0.0",
     "cross-spawn": "^7.0.2",
     "debug": "^4.0.1",
     "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",
-    "esquery": "^1.2.0",
+    "esquery": "^1.4.0",
     "esutils": "^2.0.2",
-    "file-entry-cache": "^6.0.0",
+    "fast-deep-equal": "^3.1.3",
+    "file-entry-cache": "^6.0.1",
     "functional-red-black-tree": "^1.0.1",
-    "glob-parent": "^5.0.0",
-    "globals": "^12.1.0",
+    "glob-parent": "^5.1.2",
+    "globals": "^13.6.0",
     "ignore": "^4.0.6",
     "import-fresh": "^3.0.0",
     "imurmurhash": "^0.1.4",
@@ -71,7 +70,7 @@
     "js-yaml": "^3.13.1",
     "json-stable-stringify-without-jsonify": "^1.0.1",
     "levn": "^0.4.1",
-    "lodash": "^4.17.20",
+    "lodash.merge": "^4.6.2",
     "minimatch": "^3.0.4",
     "natural-compare": "^1.4.0",
     "optionator": "^0.9.1",
     "semver": "^7.2.1",
     "strip-ansi": "^6.0.0",
     "strip-json-comments": "^3.1.0",
-    "table": "^6.0.4",
+    "table": "^6.0.9",
     "text-table": "^0.2.0",
     "v8-compile-cache": "^2.0.3"
   },
   "devDependencies": {
     "@babel/core": "^7.4.3",
     "@babel/preset-env": "^7.4.3",
-    "acorn": "^7.2.0",
     "babel-loader": "^8.0.5",
     "chai": "^4.0.1",
     "cheerio": "^0.22.0",
     "core-js": "^3.1.3",
     "dateformat": "^3.0.3",
     "ejs": "^3.0.2",
-    "escape-string-regexp": "^3.0.0",
     "eslint": "file:.",
     "eslint-config-eslint": "file:packages/eslint-config-eslint",
-    "eslint-plugin-eslint-plugin": "^2.2.1",
+    "eslint-plugin-eslint-plugin": "^3.0.3",
     "eslint-plugin-internal-rules": "file:tools/internal-rules",
-    "eslint-plugin-jsdoc": "^22.1.0",
+    "eslint-plugin-jsdoc": "^25.4.3",
     "eslint-plugin-node": "^11.1.0",
     "eslint-release": "^2.0.0",
-    "eslump": "^2.0.0",
+    "eslump": "^3.0.0",
     "esprima": "^4.0.1",
     "fs-teardown": "^0.1.0",
     "glob": "^7.1.6",
     "jsdoc": "^3.5.5",
-    "karma": "^4.0.1",
+    "karma": "^6.1.1",
     "karma-chrome-launcher": "^3.1.0",
-    "karma-mocha": "^1.3.0",
-    "karma-mocha-reporter": "^2.2.3",
-    "karma-webpack": "^4.0.0-rc.6",
+    "karma-mocha": "^2.0.1",
+    "karma-mocha-reporter": "^2.2.5",
+    "karma-webpack": "^5.0.0",
     "lint-staged": "^10.1.2",
     "load-perf": "^0.2.0",
     "markdownlint": "^0.19.0",
     "markdownlint-cli": "^0.22.0",
     "memfs": "^3.0.1",
-    "mocha": "^7.1.1",
-    "mocha-junit-reporter": "^1.23.0",
+    "mocha": "^8.3.2",
+    "mocha-junit-reporter": "^2.0.0",
+    "node-polyfill-webpack-plugin": "^1.0.3",
     "npm-license": "^0.3.3",
     "nyc": "^15.0.1",
     "proxyquire": "^2.0.1",
-    "puppeteer": "^4.0.0",
+    "puppeteer": "^7.1.0",
     "recast": "^0.19.0",
     "regenerator-runtime": "^0.13.2",
     "shelljs": "^0.8.2",
     "sinon": "^9.0.1",
     "temp": "^0.9.0",
-    "webpack": "^4.35.0",
-    "webpack-cli": "^3.3.5",
+    "webpack": "^5.23.0",
+    "webpack-cli": "^4.5.0",
     "yorkie": "^2.0.0"
   },
   "keywords": [
index c94e88568d4f35c766ef2b4f503b46eebfad7c7e..a3db1ac6c8d38a93553d3bb63fe27216ebc7cc33 100644 (file)
@@ -57078,7 +57078,7 @@ var JSHINT = (function () {
   FutureReservedWord("transient");
   FutureReservedWord("volatile");
 
-  // this function is used to determine wether a squarebracket or a curlybracket
+  // this function is used to determine whether a squarebracket or a curlybracket
   // expression is a comprehension array, destructuring assignment or a json value.
 
   var lookupBlockType = function () {
@@ -60568,4 +60568,4 @@ exports.yui = {
 },{}]},{},["nr+AlQ"])
 JSHINT = require('jshint').JSHINT;
 if (typeof exports === 'object' && exports) exports.JSHINT = JSHINT;
-}());
\ No newline at end of file
+}());
index c39795086e750152c1e25c5541cbcca038e3d6e0..8b9a039b2e6dca1b89d8301cbaa14bdeaac269be 100644 (file)
@@ -1,3 +1,3 @@
 {
-    "extends": "./non-existant.json"
+    "extends": "./non-existent.json"
 }
index c467ac7367f3cba795f59c5dd269802a9c39e5ed..263143645b4169f2a9efb71a73f8cd1e73625dfe 100644 (file)
@@ -1,5 +1,5 @@
 /*global module*/
-var nonExistantFormatter = require('this-module-does-not-exist');
+var nonExistentFormatter = require('this-module-does-not-exist');
 module.exports = function(results) {
-    return nonExistantFormatter(results);
+    return nonExistentFormatter(results);
 };
index 9e7c49c84d7cc1113cfb46de09804a33b6dc542e..59243b0b7dcf3273eb2bba6a4551dd15573c53e8 100644 (file)
@@ -2702,6 +2702,127 @@ describe("CLIEngine", () => {
                     assert.deepStrictEqual(result, cachedResult, "result is the same with or without cache");
                 });
             });
+
+            describe("cacheStrategy", () => {
+                it("should detect changes using a file's modification time when set to 'metadata'", () => {
+                    const cacheFile = getFixturePath(".eslintcache");
+                    const badFile = getFixturePath("cache/src", "fail-file.js");
+                    const goodFile = getFixturePath("cache/src", "test-file.js");
+
+                    doDelete(cacheFile);
+
+                    engine = new CLIEngine({
+                        cwd: path.join(fixtureDir, ".."),
+                        useEslintrc: false,
+
+                        // specifying cache true the cache will be created
+                        cache: true,
+                        cacheFile,
+                        cacheStrategy: "metadata",
+                        rules: {
+                            "no-console": 0,
+                            "no-unused-vars": 2
+                        },
+                        extensions: ["js"]
+                    });
+
+                    engine.executeOnFiles([badFile, goodFile]);
+
+                    let fileCache = fCache.createFromFile(cacheFile, false);
+                    const entries = fileCache.normalizeEntries([badFile, goodFile]);
+
+                    entries.forEach(entry => {
+                        assert.isFalse(entry.changed, `the entry for ${entry.key} is initially unchanged`);
+                    });
+
+                    // this should result in a changed entry
+                    shell.touch(goodFile);
+                    fileCache = fCache.createFromFile(cacheFile, false);
+                    assert.isFalse(fileCache.getFileDescriptor(badFile).changed, `the entry for ${badFile} is unchanged`);
+                    assert.isTrue(fileCache.getFileDescriptor(goodFile).changed, `the entry for ${goodFile} is changed`);
+                });
+
+                it("should not detect changes using a file's modification time when set to 'content'", () => {
+                    const cacheFile = getFixturePath(".eslintcache");
+                    const badFile = getFixturePath("cache/src", "fail-file.js");
+                    const goodFile = getFixturePath("cache/src", "test-file.js");
+
+                    doDelete(cacheFile);
+
+                    engine = new CLIEngine({
+                        cwd: path.join(fixtureDir, ".."),
+                        useEslintrc: false,
+
+                        // specifying cache true the cache will be created
+                        cache: true,
+                        cacheFile,
+                        cacheStrategy: "content",
+                        rules: {
+                            "no-console": 0,
+                            "no-unused-vars": 2
+                        },
+                        extensions: ["js"]
+                    });
+
+                    engine.executeOnFiles([badFile, goodFile]);
+
+                    let fileCache = fCache.createFromFile(cacheFile, true);
+                    let entries = fileCache.normalizeEntries([badFile, goodFile]);
+
+                    entries.forEach(entry => {
+                        assert.isFalse(entry.changed, `the entry for ${entry.key} is initially unchanged`);
+                    });
+
+                    // this should NOT result in a changed entry
+                    shell.touch(goodFile);
+                    fileCache = fCache.createFromFile(cacheFile, true);
+                    entries = fileCache.normalizeEntries([badFile, goodFile]);
+                    entries.forEach(entry => {
+                        assert.isFalse(entry.changed, `the entry for ${entry.key} remains unchanged`);
+                    });
+                });
+
+                it("should detect changes using a file's contents when set to 'content'", () => {
+                    const cacheFile = getFixturePath(".eslintcache");
+                    const badFile = getFixturePath("cache/src", "fail-file.js");
+                    const goodFile = getFixturePath("cache/src", "test-file.js");
+                    const goodFileCopy = path.resolve(`${path.dirname(goodFile)}`, "test-file-copy.js");
+
+                    shell.cp(goodFile, goodFileCopy);
+
+                    doDelete(cacheFile);
+
+                    engine = new CLIEngine({
+                        cwd: path.join(fixtureDir, ".."),
+                        useEslintrc: false,
+
+                        // specifying cache true the cache will be created
+                        cache: true,
+                        cacheFile,
+                        cacheStrategy: "content",
+                        rules: {
+                            "no-console": 0,
+                            "no-unused-vars": 2
+                        },
+                        extensions: ["js"]
+                    });
+
+                    engine.executeOnFiles([badFile, goodFileCopy]);
+
+                    let fileCache = fCache.createFromFile(cacheFile, true);
+                    const entries = fileCache.normalizeEntries([badFile, goodFileCopy]);
+
+                    entries.forEach(entry => {
+                        assert.isFalse(entry.changed, `the entry for ${entry.key} is initially unchanged`);
+                    });
+
+                    // this should result in a changed entry
+                    shell.sed("-i", "abc", "xzy", goodFileCopy);
+                    fileCache = fCache.createFromFile(cacheFile, true);
+                    assert.isFalse(fileCache.getFileDescriptor(badFile).changed, `the entry for ${badFile} is unchanged`);
+                    assert.isTrue(fileCache.getFileDescriptor(goodFileCopy).changed, `the entry for ${goodFileCopy} is changed`);
+                });
+            });
         });
 
         describe("processors", () => {
index 3ea51a408c8872b4a7404237facff8643a44149e..1ea7a3bd2c4fa9014f4a08b9c186277cd97a0da6 100644 (file)
@@ -487,6 +487,50 @@ describe("FileEnumerator", () => {
                 });
             });
         });
+
+        describe("if contains symbolic links", async () => {
+            const root = path.join(os.tmpdir(), "eslint/file-enumerator");
+            const files = {
+                "dir1/1.js": "",
+                "dir1/2.js": "",
+                "top-level.js": "",
+                ".eslintrc.json": JSON.stringify({ rules: {} })
+            };
+            const dir2 = path.join(root, "dir2");
+            const { prepare, cleanup } = createCustomTeardown({ cwd: root, files });
+
+            beforeEach(async () => {
+                await prepare();
+                fs.mkdirSync(dir2);
+                fs.symlinkSync(path.join(root, "top-level.js"), path.join(dir2, "top.js"), "file");
+                fs.symlinkSync(path.join(root, "dir1"), path.join(dir2, "nested"), "dir");
+            });
+
+            afterEach(cleanup);
+
+            it("should resolve", () => {
+                const enumerator = new FileEnumerator({ cwd: root });
+                const list = Array.from(enumerator.iterateFiles(["dir2/**/*.js"])).map(({ filePath }) => filePath);
+
+                assert.deepStrictEqual(list, [
+                    path.join(dir2, "nested", "1.js"),
+                    path.join(dir2, "nested", "2.js"),
+                    path.join(dir2, "top.js")
+                ]);
+            });
+
+            it("should ignore broken links", () => {
+                fs.unlinkSync(path.join(root, "top-level.js"));
+
+                const enumerator = new FileEnumerator({ cwd: root });
+                const list = Array.from(enumerator.iterateFiles(["dir2/**/*.js"])).map(({ filePath }) => filePath);
+
+                assert.deepStrictEqual(list, [
+                    path.join(dir2, "nested", "1.js"),
+                    path.join(dir2, "nested", "2.js")
+                ]);
+            });
+        });
     });
 
     // https://github.com/eslint/eslint/issues/13789
index a3b6ee3ab2c0ef3626d520676b95bbf4bb283b48..41c72308f42769720d1ac1d76c733ce192271f7e 100644 (file)
@@ -20,18 +20,23 @@ const assert = require("chai").assert,
 //-----------------------------------------------------------------------------
 
 describe("LintResultCache", () => {
-    const fixturePath = path.resolve(__dirname, "../../fixtures/lint-result-cache");
+    const fixturePath = path.resolve(
+        __dirname,
+        "../../fixtures/lint-result-cache"
+    );
     const cacheFileLocation = path.join(fixturePath, ".eslintcache");
     const fileEntryCacheStubs = {};
 
     let LintResultCache,
         hashStub,
+        sandbox,
         fakeConfig,
         fakeErrorResults,
         fakeErrorResultsAutofix;
 
     before(() => {
-        hashStub = sinon.stub();
+        sandbox = sinon.createSandbox();
+        hashStub = sandbox.stub();
 
         let shouldFix = false;
 
@@ -44,21 +49,28 @@ describe("LintResultCache", () => {
         });
 
         // Get results without autofixing...
-        fakeErrorResults = cliEngine.executeOnFiles([path.join(fixturePath, "test-with-errors.js")]).results[0];
+        fakeErrorResults = cliEngine.executeOnFiles([
+            path.join(fixturePath, "test-with-errors.js")
+        ]).results[0];
 
         // ...and with autofixing
         shouldFix = true;
-        fakeErrorResultsAutofix = cliEngine.executeOnFiles([path.join(fixturePath, "test-with-errors.js")]).results[0];
+        fakeErrorResultsAutofix = cliEngine.executeOnFiles([
+            path.join(fixturePath, "test-with-errors.js")
+        ]).results[0];
 
         // Set up LintResultCache with fake fileEntryCache module
-        LintResultCache = proxyquire("../../../lib/cli-engine/lint-result-cache.js", {
-            "file-entry-cache": fileEntryCacheStubs,
-            "./hash": hashStub
-        });
+        LintResultCache = proxyquire(
+            "../../../lib/cli-engine/lint-result-cache.js",
+            {
+                "file-entry-cache": fileEntryCacheStubs,
+                "./hash": hashStub
+            }
+        );
     });
 
     afterEach(done => {
-        sinon.reset();
+        sandbox.reset();
 
         fs.unlink(cacheFileLocation, err => {
             if (err && err.code !== "ENOENT") {
@@ -71,11 +83,28 @@ describe("LintResultCache", () => {
 
     describe("constructor", () => {
         it("should throw an error if cache file path is not provided", () => {
-            assert.throws(() => new LintResultCache(), /Cache file location is required/u);
+            assert.throws(
+                () => new LintResultCache(),
+                /Cache file location is required/u
+            );
+        });
+
+        it("should throw an error if cacheStrategy is not provided", () => {
+            assert.throws(
+                () => new LintResultCache(cacheFileLocation),
+                /Cache strategy is required/u
+            );
         });
 
-        it("should successfully create an instance if cache file location is provided", () => {
-            const instance = new LintResultCache(cacheFileLocation);
+        it("should throw an error if cacheStrategy is an invalid value", () => {
+            assert.throws(
+                () => new LintResultCache(cacheFileLocation, "foo"),
+                /Cache strategy must be one of/u
+            );
+        });
+
+        it("should successfully create an instance if cache file location and cache strategy provided", () => {
+            const instance = new LintResultCache(cacheFileLocation, "metadata");
 
             assert.ok(instance, "Instance should have been created successfully");
         });
@@ -85,12 +114,10 @@ describe("LintResultCache", () => {
         const filePath = path.join(fixturePath, "test-with-errors.js");
         const hashOfConfig = "hashOfConfig";
 
-        let cacheEntry,
-            getFileDescriptorStub,
-            lintResultsCache;
+        let cacheEntry, getFileDescriptorStub, lintResultsCache;
 
         before(() => {
-            getFileDescriptorStub = sinon.stub();
+            getFileDescriptorStub = sandbox.stub();
 
             fileEntryCacheStubs.create = () => ({
                 getFileDescriptor: getFileDescriptorStub
@@ -112,12 +139,11 @@ describe("LintResultCache", () => {
                 }
             };
 
-            getFileDescriptorStub.withArgs(filePath)
-                .returns(cacheEntry);
+            getFileDescriptorStub.withArgs(filePath).returns(cacheEntry);
 
             fakeConfig = {};
 
-            lintResultsCache = new LintResultCache(cacheFileLocation);
+            lintResultsCache = new LintResultCache(cacheFileLocation, "metadata");
         });
 
         describe("when calculating the hashing", () => {
@@ -127,7 +153,7 @@ describe("LintResultCache", () => {
                     "../../package.json": { version },
                     "./hash": hashStub
                 });
-                const newLintResultCache = new NewLintResultCache(cacheFileLocation);
+                const newLintResultCache = new NewLintResultCache(cacheFileLocation, "metadata");
 
                 newLintResultCache.getCachedLintResults(filePath, fakeConfig);
                 assert.ok(hashStub.calledOnce);
@@ -137,13 +163,14 @@ describe("LintResultCache", () => {
             it("contains node version during hashing", () => {
                 const version = "node-=-version";
 
-                sinon.stub(process, "version").value(version);
+                sandbox.stub(process, "version").value(version);
                 const NewLintResultCache = proxyquire("../../../lib/cli-engine/lint-result-cache.js", {
                     "./hash": hashStub
                 });
-                const newLintResultCache = new NewLintResultCache(cacheFileLocation);
+                const newLintResultCache = new NewLintResultCache(cacheFileLocation, "metadata");
 
                 newLintResultCache.getCachedLintResults(filePath, fakeConfig);
+
                 assert.ok(hashStub.calledOnce);
                 assert.ok(hashStub.calledWithMatch(version));
             });
@@ -156,7 +183,10 @@ describe("LintResultCache", () => {
             });
 
             it("should return null", () => {
-                const result = lintResultsCache.getCachedLintResults(filePath, fakeConfig);
+                const result = lintResultsCache.getCachedLintResults(
+                    filePath,
+                    fakeConfig
+                );
 
                 assert.ok(getFileDescriptorStub.calledOnce);
                 assert.isNull(result);
@@ -169,7 +199,10 @@ describe("LintResultCache", () => {
             });
 
             it("should return null", () => {
-                const result = lintResultsCache.getCachedLintResults(filePath, fakeConfig);
+                const result = lintResultsCache.getCachedLintResults(
+                    filePath,
+                    fakeConfig
+                );
 
                 assert.ok(getFileDescriptorStub.calledOnce);
                 assert.isNull(result);
@@ -183,7 +216,10 @@ describe("LintResultCache", () => {
             });
 
             it("should return null", () => {
-                const result = lintResultsCache.getCachedLintResults(filePath, fakeConfig);
+                const result = lintResultsCache.getCachedLintResults(
+                    filePath,
+                    fakeConfig
+                );
 
                 assert.ok(getFileDescriptorStub.calledOnce);
                 assert.isNull(result);
@@ -196,10 +232,16 @@ describe("LintResultCache", () => {
             });
 
             it("should return expected results", () => {
-                const result = lintResultsCache.getCachedLintResults(filePath, fakeConfig);
+                const result = lintResultsCache.getCachedLintResults(
+                    filePath,
+                    fakeConfig
+                );
 
                 assert.deepStrictEqual(result, fakeErrorResults);
-                assert.ok(result.source, "source property should be hydrated from filesystem");
+                assert.ok(
+                    result.source,
+                    "source property should be hydrated from filesystem"
+                );
             });
         });
     });
@@ -208,12 +250,10 @@ describe("LintResultCache", () => {
         const filePath = path.join(fixturePath, "test-with-errors.js");
         const hashOfConfig = "hashOfConfig";
 
-        let cacheEntry,
-            getFileDescriptorStub,
-            lintResultsCache;
+        let cacheEntry, getFileDescriptorStub, lintResultsCache;
 
         before(() => {
-            getFileDescriptorStub = sinon.stub();
+            getFileDescriptorStub = sandbox.stub();
 
             fileEntryCacheStubs.create = () => ({
                 getFileDescriptor: getFileDescriptorStub
@@ -229,19 +269,22 @@ describe("LintResultCache", () => {
                 meta: {}
             };
 
-            getFileDescriptorStub.withArgs(filePath)
-                .returns(cacheEntry);
+            getFileDescriptorStub.withArgs(filePath).returns(cacheEntry);
 
             fakeConfig = {};
 
             hashStub.returns(hashOfConfig);
 
-            lintResultsCache = new LintResultCache(cacheFileLocation);
+            lintResultsCache = new LintResultCache(cacheFileLocation, "metadata");
         });
 
         describe("When lint result has output property", () => {
             it("does not modify file entry", () => {
-                lintResultsCache.setCachedLintResults(filePath, fakeConfig, fakeErrorResultsAutofix);
+                lintResultsCache.setCachedLintResults(
+                    filePath,
+                    fakeConfig,
+                    fakeErrorResultsAutofix
+                );
 
                 assert.notProperty(cacheEntry.meta, "results");
                 assert.notProperty(cacheEntry.meta, "hashOfConfig");
@@ -254,7 +297,11 @@ describe("LintResultCache", () => {
             });
 
             it("does not modify file entry", () => {
-                lintResultsCache.setCachedLintResults(filePath, fakeConfig, fakeErrorResults);
+                lintResultsCache.setCachedLintResults(
+                    filePath,
+                    fakeConfig,
+                    fakeErrorResults
+                );
 
                 assert.notProperty(cacheEntry.meta, "results");
                 assert.notProperty(cacheEntry.meta, "hashOfConfig");
@@ -263,7 +310,11 @@ describe("LintResultCache", () => {
 
         describe("When file is found on filesystem", () => {
             beforeEach(() => {
-                lintResultsCache.setCachedLintResults(filePath, fakeConfig, fakeErrorResults);
+                lintResultsCache.setCachedLintResults(
+                    filePath,
+                    fakeConfig,
+                    fakeErrorResults
+                );
             });
 
             it("stores hash of config in file entry", () => {
@@ -271,11 +322,9 @@ describe("LintResultCache", () => {
             });
 
             it("stores results (except source) in file entry", () => {
-                const expectedCachedResults = Object.assign(
-                    {},
-                    fakeErrorResults,
-                    { source: null }
-                );
+                const expectedCachedResults = Object.assign({}, fakeErrorResults, {
+                    source: null
+                });
 
                 assert.deepStrictEqual(cacheEntry.meta.results, expectedCachedResults);
             });
@@ -295,11 +344,9 @@ describe("LintResultCache", () => {
             });
 
             it("stores results (except source) in file entry", () => {
-                const expectedCachedResults = Object.assign(
-                    {},
-                    fakeErrorResults,
-                    { source: null }
-                );
+                const expectedCachedResults = Object.assign({}, fakeErrorResults, {
+                    source: null
+                });
 
                 assert.deepStrictEqual(cacheEntry.meta.results, expectedCachedResults);
             });
@@ -307,11 +354,10 @@ describe("LintResultCache", () => {
     });
 
     describe("reconcile", () => {
-        let reconcileStub,
-            lintResultsCache;
+        let reconcileStub, lintResultsCache;
 
         before(() => {
-            reconcileStub = sinon.stub();
+            reconcileStub = sandbox.stub();
 
             fileEntryCacheStubs.create = () => ({
                 reconcile: reconcileStub
@@ -323,7 +369,7 @@ describe("LintResultCache", () => {
         });
 
         beforeEach(() => {
-            lintResultsCache = new LintResultCache(cacheFileLocation);
+            lintResultsCache = new LintResultCache(cacheFileLocation, "metadata");
         });
 
         it("calls reconcile on the underlying cache", () => {
index 0f8a24c2e28975cb22da24b82bc25ed6779877a7..d1fea182db1472cb95202cc50ab21391980120df 100644 (file)
@@ -778,6 +778,16 @@ describe("cli", () => {
             assert.include(log.error.getCall(0).args[0], "ESLint found too many warnings");
         });
 
+        it("should exit with exit code 1 without printing warnings if the quiet option is enabled and warning count exceeds threshold", async () => {
+            const filePath = getFixturePath("max-warnings");
+            const exitCode = await cli.execute(`--no-ignore --quiet --max-warnings 5 ${filePath}`);
+
+            assert.strictEqual(exitCode, 1);
+            assert.ok(log.error.calledOnce);
+            assert.include(log.error.getCall(0).args[0], "ESLint found too many warnings");
+            assert.ok(log.info.notCalled); // didn't print warnings
+        });
+
         it("should not change exit code if warning count equals threshold", async () => {
             const filePath = getFixturePath("max-warnings");
             const exitCode = await cli.execute(`--no-ignore --max-warnings 6 ${filePath}`);
index 530464ecaa19d8a2078d0c95732ba7f37e7213e9..c7783a6c059e09f3e23e2ef5a7c7a9d8359b46d1 100644 (file)
@@ -2650,6 +2650,130 @@ describe("ESLint", () => {
                     assert.deepStrictEqual(result, cachedResult, "result is the same with or without cache");
                 });
             });
+
+            describe("cacheStrategy", () => {
+                it("should detect changes using a file's modification time when set to 'metadata'", async () => {
+                    const cacheLocation = getFixturePath(".eslintcache");
+
+                    doDelete(cacheLocation);
+
+                    eslint = new ESLint({
+                        cwd: path.join(fixtureDir, ".."),
+                        useEslintrc: false,
+
+                        // specifying cache true the cache will be created
+                        cache: true,
+                        cacheLocation,
+                        cacheStrategy: "metadata",
+                        overrideConfig: {
+                            rules: {
+                                "no-console": 0,
+                                "no-unused-vars": 2
+                            }
+                        },
+                        extensions: ["js"]
+                    });
+                    const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js"));
+                    const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js"));
+
+                    await eslint.lintFiles([badFile, goodFile]);
+                    let fileCache = fCache.createFromFile(cacheLocation);
+                    const entries = fileCache.normalizeEntries([badFile, goodFile]);
+
+                    entries.forEach(entry => {
+                        assert(entry.changed === false, `the entry for ${entry.key} is initially unchanged`);
+                    });
+
+                    // this should result in a changed entry
+                    shell.touch(goodFile);
+                    fileCache = fCache.createFromFile(cacheLocation);
+                    assert(fileCache.getFileDescriptor(badFile).changed === false, `the entry for ${badFile} is unchanged`);
+                    assert(fileCache.getFileDescriptor(goodFile).changed === true, `the entry for ${goodFile} is changed`);
+                });
+
+                it("should not detect changes using a file's modification time when set to 'content'", async () => {
+                    const cacheLocation = getFixturePath(".eslintcache");
+
+                    doDelete(cacheLocation);
+
+                    eslint = new ESLint({
+                        cwd: path.join(fixtureDir, ".."),
+                        useEslintrc: false,
+
+                        // specifying cache true the cache will be created
+                        cache: true,
+                        cacheLocation,
+                        cacheStrategy: "content",
+                        overrideConfig: {
+                            rules: {
+                                "no-console": 0,
+                                "no-unused-vars": 2
+                            }
+                        },
+                        extensions: ["js"]
+                    });
+                    const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js"));
+                    const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js"));
+
+                    await eslint.lintFiles([badFile, goodFile]);
+                    let fileCache = fCache.createFromFile(cacheLocation, true);
+                    let entries = fileCache.normalizeEntries([badFile, goodFile]);
+
+                    entries.forEach(entry => {
+                        assert(entry.changed === false, `the entry for ${entry.key} is initially unchanged`);
+                    });
+
+                    // this should NOT result in a changed entry
+                    shell.touch(goodFile);
+                    fileCache = fCache.createFromFile(cacheLocation, true);
+                    entries = fileCache.normalizeEntries([badFile, goodFile]);
+                    entries.forEach(entry => {
+                        assert(entry.changed === false, `the entry for ${entry.key} remains unchanged`);
+                    });
+                });
+
+                it("should detect changes using a file's contents when set to 'content'", async () => {
+                    const cacheLocation = getFixturePath(".eslintcache");
+
+                    doDelete(cacheLocation);
+
+                    eslint = new ESLint({
+                        cwd: path.join(fixtureDir, ".."),
+                        useEslintrc: false,
+
+                        // specifying cache true the cache will be created
+                        cache: true,
+                        cacheLocation,
+                        cacheStrategy: "content",
+                        overrideConfig: {
+                            rules: {
+                                "no-console": 0,
+                                "no-unused-vars": 2
+                            }
+                        },
+                        extensions: ["js"]
+                    });
+                    const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js"));
+                    const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js"));
+                    const goodFileCopy = path.resolve(`${path.dirname(goodFile)}`, "test-file-copy.js");
+
+                    shell.cp(goodFile, goodFileCopy);
+
+                    await eslint.lintFiles([badFile, goodFileCopy]);
+                    let fileCache = fCache.createFromFile(cacheLocation, true);
+                    const entries = fileCache.normalizeEntries([badFile, goodFileCopy]);
+
+                    entries.forEach(entry => {
+                        assert(entry.changed === false, `the entry for ${entry.key} is initially unchanged`);
+                    });
+
+                    // this should result in a changed entry
+                    shell.sed("-i", "abc", "xzy", goodFileCopy);
+                    fileCache = fCache.createFromFile(cacheLocation, true);
+                    assert(fileCache.getFileDescriptor(badFile).changed === false, `the entry for ${badFile} is unchanged`);
+                    assert(fileCache.getFileDescriptor(goodFileCopy).changed === true, `the entry for ${goodFileCopy} is changed`);
+                });
+            });
         });
 
         describe("processors", () => {
@@ -2793,6 +2917,54 @@ describe("ESLint", () => {
                 assert.strictEqual(results[0].messages[0].ruleId, "post-processed");
             });
 
+            it("should run processors when calling lintText with processor resolves same extension but different content correctly", async () => {
+                let count = 0;
+
+                eslint = new ESLint({
+                    useEslintrc: false,
+                    overrideConfig: {
+                        plugins: ["test-processor"],
+                        overrides: [{
+                            files: ["**/*.txt/*.txt"],
+                            rules: {
+                                "no-console": 2,
+                                "no-unused-vars": 2
+                            }
+                        }]
+                    },
+                    extensions: ["txt"],
+                    ignore: false,
+                    plugins: {
+                        "test-processor": {
+                            processors: {
+                                ".txt": {
+                                    preprocess(text) {
+                                        count++;
+                                        return [
+                                            {
+
+                                                // it will be run twice, and text will be as-is at the second time, then it will not run third time
+                                                text: text.replace("a()", "b()"),
+                                                filename: ".txt"
+                                            }
+                                        ];
+                                    },
+                                    postprocess(messages) {
+                                        messages[0][0].ruleId = "post-processed";
+                                        return messages[0];
+                                    }
+                                }
+                            }
+                        }
+                    }
+                });
+                const results = await eslint.lintText("function a() {console.log(\"Test\");}", { filePath: "tests/fixtures/processors/test/test-processor.txt" });
+
+                assert.strictEqual(count, 2);
+                assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used.");
+                assert.strictEqual(results[0].messages[0].ruleId, "post-processed");
+            });
+
             describe("autofixing with processors", () => {
                 const HTML_PROCESSOR = Object.freeze({
                     preprocess(text) {
@@ -3886,6 +4058,26 @@ describe("ESLint", () => {
             assert.deepStrictEqual(actualConfig, expectedConfig);
         });
 
+        it("should return the config for a file that doesn't exist", async () => {
+            const engine = new ESLint();
+            const filePath = getFixturePath("does_not_exist.js");
+            const existingSiblingFilePath = getFixturePath("single-quoted.js");
+            const actualConfig = await engine.calculateConfigForFile(filePath);
+            const expectedConfig = await engine.calculateConfigForFile(existingSiblingFilePath);
+
+            assert.deepStrictEqual(actualConfig, expectedConfig);
+        });
+
+        it("should return the config for a virtual file that is a child of an existing file", async () => {
+            const engine = new ESLint();
+            const parentFileName = "single-quoted.js";
+            const filePath = getFixturePath(parentFileName, "virtual.js"); // single-quoted.js/virtual.js
+            const parentFilePath = getFixturePath(parentFileName);
+            const actualConfig = await engine.calculateConfigForFile(filePath);
+            const expectedConfig = await engine.calculateConfigForFile(parentFilePath);
+
+            assert.deepStrictEqual(actualConfig, expectedConfig);
+        });
 
         it("should return the config when run from within a subdir", async () => {
             const options = {
index 1fa2b0476dea016e6676d59f1e76b052da17edeb..3db6a63645f9399a239b1dabcb8f90dfd52c7f31 100644 (file)
@@ -132,6 +132,20 @@ describe("autoconfig", () => {
                 assert.include(Object.keys(registry.rules), "eqeqeq");
             });
 
+            it("should not add deprecated rules", () => {
+                const registry = new autoconfig.Registry();
+
+                registry.populateFromCoreRules();
+
+                const { rules } = registry;
+
+                assert.notProperty(rules, "id-blacklist");
+                assert.notProperty(rules, "no-negated-in-lhs");
+                assert.notProperty(rules, "no-process-exit");
+                assert.notProperty(rules, "no-spaced-func");
+                assert.notProperty(rules, "prefer-reflect");
+            });
+
             it("should not add duplicate rules", () => {
                 const registry = new autoconfig.Registry(rulesConfig);
 
index d607ccff52f4535b840dbe90f38f102667bd9fa9..81e4e52faaa90a723bde15525de418ab9e237aeb 100644 (file)
@@ -27,6 +27,8 @@ const proxyquire = require("proxyquire").noPreserveCache();
 //------------------------------------------------------------------------------
 
 let answers = {};
+let pkgJSONContents = {};
+let pkgJSONPath = "";
 
 describe("configInitializer", () => {
 
@@ -240,6 +242,14 @@ describe("configInitializer", () => {
                 assert.include(modules, "eslint-config-standard@latest");
             });
 
+            it("should support the xo style guide", () => {
+                const config = { extends: "xo" };
+                const modules = init.getModulesList(config);
+
+                assert.deepStrictEqual(config, { extends: "xo", installedESLint: true });
+                assert.include(modules, "eslint-config-xo@latest");
+            });
+
             it("should install required sharable config", () => {
                 const config = { extends: "google" };
 
@@ -409,6 +419,14 @@ describe("configInitializer", () => {
                 assert.notProperty(config.rules, "no-debugger");
             });
 
+            it("should not include deprecated rules", () => {
+                assert.notProperty(config.rules, "id-blacklist");
+                assert.notProperty(config.rules, "no-negated-in-lhs");
+                assert.notProperty(config.rules, "no-process-exit");
+                assert.notProperty(config.rules, "no-spaced-func");
+                assert.notProperty(config.rules, "prefer-reflect");
+            });
+
             it("should support new ES features if using later ES version", () => {
                 const filename = getFixturePath("new-es-features");
 
@@ -439,4 +457,121 @@ describe("configInitializer", () => {
             });
         });
     });
+
+    describe("writeFile()", () => {
+
+        beforeEach(() => {
+            answers = {
+                purpose: "style",
+                source: "prompt",
+                extendDefault: true,
+                indent: 2,
+                quotes: "single",
+                linebreak: "unix",
+                semi: true,
+                moduleType: "esm",
+                es6Globals: true,
+                env: ["browser"],
+                format: "JSON"
+            };
+
+            pkgJSONContents = {
+                name: "config-initializer",
+                version: "1.0.0"
+            };
+
+            process.chdir(fixtureDir);
+
+            pkgJSONPath = path.resolve(fixtureDir, "package.json");
+        });
+
+        afterEach(() => {
+            process.chdir(originalDir);
+        });
+
+        it("should create .eslintrc.json", () => {
+            const config = init.processAnswers(answers);
+            const filePath = path.resolve(fixtureDir, ".eslintrc.json");
+
+            fs.writeFileSync(pkgJSONPath, JSON.stringify(pkgJSONContents));
+
+            init.writeFile(config, answers.format);
+
+            assert.isTrue(fs.existsSync(filePath));
+
+            fs.unlinkSync(filePath);
+            fs.unlinkSync(pkgJSONPath);
+        });
+
+        it("should create .eslintrc.js", () => {
+            answers.format = "JavaScript";
+
+            const config = init.processAnswers(answers);
+            const filePath = path.resolve(fixtureDir, ".eslintrc.js");
+
+            fs.writeFileSync(pkgJSONPath, JSON.stringify(pkgJSONContents));
+
+            init.writeFile(config, answers.format);
+
+            assert.isTrue(fs.existsSync(filePath));
+
+            fs.unlinkSync(filePath);
+            fs.unlinkSync(pkgJSONPath);
+        });
+
+        it("should create .eslintrc.yml", () => {
+            answers.format = "YAML";
+
+            const config = init.processAnswers(answers);
+            const filePath = path.resolve(fixtureDir, ".eslintrc.yml");
+
+            fs.writeFileSync(pkgJSONPath, JSON.stringify(pkgJSONContents));
+
+            init.writeFile(config, answers.format);
+
+            assert.isTrue(fs.existsSync(filePath));
+
+            fs.unlinkSync(filePath);
+            fs.unlinkSync(pkgJSONPath);
+        });
+
+        // For https://github.com/eslint/eslint/issues/14137
+        it("should create .eslintrc.cjs", () => {
+            answers.format = "JavaScript";
+
+            // create package.json with "type": "module"
+            pkgJSONContents.type = "module";
+
+            fs.writeFileSync(pkgJSONPath, JSON.stringify(pkgJSONContents));
+
+            const config = init.processAnswers(answers);
+            const filePath = path.resolve(fixtureDir, ".eslintrc.cjs");
+
+            init.writeFile(config, answers.format);
+
+            assert.isTrue(fs.existsSync(filePath));
+
+            fs.unlinkSync(filePath);
+            fs.unlinkSync(pkgJSONPath);
+        });
+
+        it("should create .eslintrc.json even with type: 'module'", () => {
+            answers.format = "JSON";
+
+            // create package.json with "type": "module"
+            pkgJSONContents.type = "module";
+
+            fs.writeFileSync(pkgJSONPath, JSON.stringify(pkgJSONContents));
+
+            const config = init.processAnswers(answers);
+            const filePath = path.resolve(fixtureDir, ".eslintrc.json");
+
+            init.writeFile(config, answers.format);
+
+            assert.isTrue(fs.existsSync(filePath));
+
+            fs.unlinkSync(filePath);
+            fs.unlinkSync(pkgJSONPath);
+        });
+    });
 });
index c5d0ecf267f4c01403ac8b837933041c6732b9eb..b2356f24691b64deba14407fa26a9ccb940584ab 100644 (file)
@@ -12,6 +12,7 @@
 const assert = require("assert"),
     fs = require("fs"),
     path = require("path"),
+    vk = require("eslint-visitor-keys"),
     { Linter } = require("../../../../lib/linter"),
     EventGeneratorTester = require("../../../../tools/internal-testers/event-generator-tester"),
     createEmitter = require("../../../../lib/linter/safe-emitter"),
@@ -19,12 +20,15 @@ const assert = require("assert"),
     CodePath = require("../../../../lib/linter/code-path-analysis/code-path"),
     CodePathAnalyzer = require("../../../../lib/linter/code-path-analysis/code-path-analyzer"),
     CodePathSegment = require("../../../../lib/linter/code-path-analysis/code-path-segment"),
-    NodeEventGenerator = require("../../../../lib/linter/node-event-generator");
+    NodeEventGenerator = require("../../../../lib/linter/node-event-generator"),
+    Traverser = require("../../../lib/shared/traverser");
 
 //------------------------------------------------------------------------------
 // Helpers
 //------------------------------------------------------------------------------
 
+const STANDARD_ESQUERY_OPTION = { visitorKeys: vk.KEYS, fallback: Traverser.getKeys };
+
 const expectedPattern = /\/\*expected\s+((?:.|[\r\n])+?)\s*\*\//gu;
 const lineEndingPattern = /\r?\n/gu;
 const linter = new Linter();
@@ -54,7 +58,7 @@ function getExpectedDotArrows(source) {
 
 describe("CodePathAnalyzer", () => {
     EventGeneratorTester.testEventGeneratorInterface(
-        new CodePathAnalyzer(new NodeEventGenerator(createEmitter()))
+        new CodePathAnalyzer(new NodeEventGenerator(createEmitter(), STANDARD_ESQUERY_OPTION))
     );
 
     describe("interface of code paths", () => {
index 093ebcfaf70f4016d963fc44fcbf648cc04bfafb..e61804d3ae7a5b8001a1a0aa504c5da9eef16279 100644 (file)
@@ -5,27 +5,6 @@
 
 "use strict";
 
-//------------------------------------------------------------------------------
-// Helper
-//------------------------------------------------------------------------------
-
-/**
- * To make sure this works in both browsers and Node.js
- * @param {string} name Name of the module to require
- * @param {Object} windowName name of the window
- * @returns {Object} Required object
- * @private
- */
-function compatRequire(name, windowName) {
-    if (typeof window === "object") { // eslint-disable-line no-undef
-        return window[windowName || name]; // eslint-disable-line no-undef
-    }
-    if (typeof require === "function") {
-        return require(name);
-    }
-    throw new Error(`Cannot find object '${name}'.`);
-}
-
 //------------------------------------------------------------------------------
 // Requirements
 //------------------------------------------------------------------------------
@@ -35,7 +14,7 @@ const assert = require("chai").assert,
     esprima = require("esprima"),
     testParsers = require("../../fixtures/parsers/linter-test-parsers");
 
-const { Linter } = compatRequire("../../../lib/linter", "eslint");
+const { Linter } = require("../../../lib/linter");
 
 //------------------------------------------------------------------------------
 // Constants
@@ -1580,6 +1559,22 @@ describe("Linter", () => {
             assert.strictEqual(messages[0].message, filename);
         });
 
+        it("has access to the physicalFilename", () => {
+            linter.defineRule(code, context => ({
+                Literal(node) {
+                    context.report(node, context.getPhysicalFilename());
+                }
+            }));
+
+            const config = { rules: {} };
+
+            config.rules[code] = 1;
+
+            const messages = linter.verify("0", config, filename);
+
+            assert.strictEqual(messages[0].message, filename);
+        });
+
         it("defaults filename to '<input>'", () => {
             linter.defineRule(code, context => ({
                 Literal(node) {
@@ -2978,6 +2973,23 @@ var a = "test2";
             assert.strictEqual(messages.length, 0);
         });
 
+        // https://github.com/eslint/eslint/issues/14652
+        it("should not report a violation", () => {
+            const codes = [
+                "/*eslint-env es6\n */ new Promise();",
+                "/*eslint-env browser,\nes6 */ window;Promise;",
+                "/*eslint-env\nbrowser,es6 */ window;Promise;"
+            ];
+            const config = { rules: { "no-undef": 1 } };
+
+            for (const code of codes) {
+                const messages = linter.verify(code, config, filename);
+
+                assert.strictEqual(messages.length, 0);
+            }
+
+        });
+
         it("should not report a violation", () => {
             const code = `/*${ESLINT_ENV} mocha,node */ require();describe();`;
 
@@ -3429,6 +3441,41 @@ var a = "test2";
             });
         });
 
+        describe("physicalFilenames", () => {
+            it("should be same as `filename` passed on options object, if no processors are used", () => {
+                const physicalFilenameChecker = sinon.spy(context => {
+                    assert.strictEqual(context.getPhysicalFilename(), "foo.js");
+                    return {};
+                });
+
+                linter.defineRule("checker", physicalFilenameChecker);
+                linter.verify("foo;", { rules: { checker: "error" } }, { filename: "foo.js" });
+                assert(physicalFilenameChecker.calledOnce);
+            });
+
+            it("should default physicalFilename to <input> when options object doesn't have filename", () => {
+                const physicalFilenameChecker = sinon.spy(context => {
+                    assert.strictEqual(context.getPhysicalFilename(), "<input>");
+                    return {};
+                });
+
+                linter.defineRule("checker", physicalFilenameChecker);
+                linter.verify("foo;", { rules: { checker: "error" } }, {});
+                assert(physicalFilenameChecker.calledOnce);
+            });
+
+            it("should default physicalFilename to <input> when only two arguments are passed", () => {
+                const physicalFilenameChecker = sinon.spy(context => {
+                    assert.strictEqual(context.getPhysicalFilename(), "<input>");
+                    return {};
+                });
+
+                linter.defineRule("checker", physicalFilenameChecker);
+                linter.verify("foo;", { rules: { checker: "error" } });
+                assert(physicalFilenameChecker.calledOnce);
+            });
+        });
+
         it("should report warnings in order by line and column when called", () => {
 
             const code = "foo()\n    alert('test')";
@@ -4804,14 +4851,17 @@ var a = "test2";
 
     describe("processors", () => {
         let receivedFilenames = [];
+        let receivedPhysicalFilenames = [];
 
         beforeEach(() => {
             receivedFilenames = [];
+            receivedPhysicalFilenames = [];
 
             // A rule that always reports the AST with a message equal to the source text
             linter.defineRule("report-original-text", context => ({
                 Program(ast) {
                     receivedFilenames.push(context.getFilename());
+                    receivedPhysicalFilenames.push(context.getPhysicalFilename());
                     context.report({ node: ast, message: context.getSourceCode().text });
                 }
             }));
@@ -4866,10 +4916,16 @@ var a = "test2";
 
                 assert.strictEqual(problems.length, 3);
                 assert.deepStrictEqual(problems.map(problem => problem.message), ["foo", "bar", "baz"]);
+
+                // filename
                 assert.strictEqual(receivedFilenames.length, 3);
                 assert(/^filename\.js[/\\]0_block\.js/u.test(receivedFilenames[0]));
                 assert(/^filename\.js[/\\]1_block\.js/u.test(receivedFilenames[1]));
                 assert(/^filename\.js[/\\]2_block\.js/u.test(receivedFilenames[2]));
+
+                // physical filename
+                assert.strictEqual(receivedPhysicalFilenames.length, 3);
+                assert.strictEqual(receivedPhysicalFilenames.every(name => name === filename), true);
             });
 
             it("should receive text even if a SourceCode object was given.", () => {
@@ -5293,9 +5349,11 @@ var a = "test2";
             let types = [];
             let sourceCode;
             let scopeManager;
+            let firstChildNodes = [];
 
             beforeEach(() => {
                 types = [];
+                firstChildNodes = [];
                 linter.defineRule("collect-node-types", () => ({
                     "*"(node) {
                         types.push(node.type);
@@ -5306,12 +5364,18 @@ var a = "test2";
 
                     return {};
                 });
+                linter.defineRule("esquery-option", () => ({
+                    ":first-child"(node) {
+                        firstChildNodes.push(node);
+                    }
+                }));
                 linter.defineParser("enhanced-parser2", testParsers.enhancedParser2);
                 linter.verify("@foo class A {}", {
                     parser: "enhanced-parser2",
                     rules: {
                         "collect-node-types": "error",
-                        "save-scope-manager": "error"
+                        "save-scope-manager": "error",
+                        "esquery-option": "error"
                     }
                 });
 
@@ -5351,6 +5415,13 @@ var a = "test2";
                     ["Program", "ClassDeclaration", "Decorator", "Identifier", "Identifier", "ClassBody"]
                 );
             });
+
+            it("esquery should use the visitorKeys (so 'visitorKeys.ClassDeclaration' includes 'experimentalDecorators')", () => {
+                assert.deepStrictEqual(
+                    firstChildNodes,
+                    [sourceCode.ast.body[0], sourceCode.ast.body[0].experimentalDecorators[0]]
+                );
+            });
         });
 
         describe("if a parser provides 'scope'", () => {
index 8d12cbbdc9d4a8d443006f004991ab0d633de308..f9010c32138c24e16148e99f5b37531b14171e0a 100644 (file)
@@ -11,6 +11,7 @@
 const assert = require("assert"),
     sinon = require("sinon"),
     espree = require("espree"),
+    vk = require("eslint-visitor-keys"),
     Traverser = require("../../../lib/shared/traverser"),
     EventGeneratorTester = require("../../../tools/internal-testers/event-generator-tester"),
     createEmitter = require("../../../lib/linter/safe-emitter"),
@@ -28,9 +29,11 @@ const ESPREE_CONFIG = {
     loc: true
 };
 
+const STANDARD_ESQUERY_OPTION = { visitorKeys: vk.KEYS, fallback: Traverser.getKeys };
+
 describe("NodeEventGenerator", () => {
     EventGeneratorTester.testEventGeneratorInterface(
-        new NodeEventGenerator(createEmitter())
+        new NodeEventGenerator(createEmitter(), STANDARD_ESQUERY_OPTION)
     );
 
     describe("entering a single AST node", () => {
@@ -40,7 +43,7 @@ describe("NodeEventGenerator", () => {
             emitter = Object.create(createEmitter(), { emit: { value: sinon.spy() } });
 
             ["Foo", "Bar", "Foo > Bar", "Foo:exit"].forEach(selector => emitter.on(selector, () => {}));
-            generator = new NodeEventGenerator(emitter);
+            generator = new NodeEventGenerator(emitter, STANDARD_ESQUERY_OPTION);
         });
 
         it("should generate events for entering AST node.", () => {
@@ -89,7 +92,7 @@ describe("NodeEventGenerator", () => {
             });
 
             possibleQueries.forEach(query => emitter.on(query, () => {}));
-            const generator = new NodeEventGenerator(emitter);
+            const generator = new NodeEventGenerator(emitter, STANDARD_ESQUERY_OPTION);
 
             Traverser.traverse(ast, {
                 enter(node, parent) {
@@ -308,13 +311,131 @@ describe("NodeEventGenerator", () => {
         );
     });
 
+    describe("traversing the entire non-standard AST", () => {
+
+        /**
+         * Gets a list of emitted types/selectors from the generator, in emission order
+         * @param {ASTNode} ast The AST to traverse
+         * @param {Record<string, string[]>} visitorKeys The custom visitor keys.
+         * @param {Array<string>|Set<string>} possibleQueries Selectors to detect
+         * @returns {Array[]} A list of emissions, in the order that they were emitted. Each emission is a two-element
+         * array where the first element is a string, and the second element is the emitted AST node.
+         */
+        function getEmissions(ast, visitorKeys, possibleQueries) {
+            const emissions = [];
+            const emitter = Object.create(createEmitter(), {
+                emit: {
+                    value: (selector, node) => emissions.push([selector, node])
+                }
+            });
+
+            possibleQueries.forEach(query => emitter.on(query, () => {}));
+            const generator = new NodeEventGenerator(emitter, { visitorKeys, fallback: Traverser.getKeys });
+
+            Traverser.traverse(ast, {
+                visitorKeys,
+                enter(node, parent) {
+                    node.parent = parent;
+                    generator.enterNode(node);
+                },
+                leave(node) {
+                    generator.leaveNode(node);
+                }
+            });
+
+            return emissions;
+        }
+
+        /**
+         * Creates a test case that asserts a particular sequence of generator emissions
+         * @param {ASTNode} ast The AST to traverse
+         * @param {Record<string, string[]>} visitorKeys The custom visitor keys.
+         * @param {string[]} possibleQueries A collection of selectors that rules are listening for
+         * @param {Array[]} expectedEmissions A function that accepts the AST and returns a list of the emissions that the
+         * generator is expected to produce, in order.
+         * Each element of this list is an array where the first element is a selector (string), and the second is an AST node
+         * This should only include emissions that appear in possibleQueries.
+         * @returns {void}
+         */
+        function assertEmissions(ast, visitorKeys, possibleQueries, expectedEmissions) {
+            it(possibleQueries.join("; "), () => {
+                const emissions = getEmissions(ast, visitorKeys, possibleQueries)
+                    .filter(emission => possibleQueries.indexOf(emission[0]) !== -1);
+
+                assert.deepStrictEqual(emissions, expectedEmissions(ast));
+            });
+        }
+
+        assertEmissions(
+            espree.parse("const foo = [<div/>, <div/>]", { ...ESPREE_CONFIG, ecmaFeatures: { jsx: true } }),
+            vk.KEYS,
+            ["* ~ *"],
+            ast => [
+                ["* ~ *", ast.body[0].declarations[0].init.elements[1]] // entering second JSXElement
+            ]
+        );
+
+        assertEmissions(
+            {
+
+                // Parse `class A implements B {}` with typescript-eslint.
+                type: "Program",
+                errors: [],
+                comments: [],
+                sourceType: "module",
+                body: [
+                    {
+                        type: "ClassDeclaration",
+                        id: {
+                            type: "Identifier",
+                            name: "A"
+                        },
+                        superClass: null,
+                        implements: [
+                            {
+                                type: "ClassImplements",
+                                id: {
+                                    type: "Identifier",
+                                    name: "B"
+                                },
+                                typeParameters: null
+                            }
+                        ],
+                        body: {
+                            type: "ClassBody",
+                            body: []
+                        }
+                    }
+                ]
+            },
+            vk.unionWith({
+
+                // see https://github.com/typescript-eslint/typescript-eslint/blob/e4d737b47574ff2c53cabab22853035dfe48c1ed/packages/visitor-keys/src/visitor-keys.ts#L27
+                ClassDeclaration: [
+                    "decorators",
+                    "id",
+                    "typeParameters",
+                    "superClass",
+                    "superTypeParameters",
+                    "implements",
+                    "body"
+                ]
+            }),
+            [":first-child"],
+            ast => [
+                [":first-child", ast.body[0]], // entering first ClassDeclaration
+                [":first-child", ast.body[0].implements[0]] // entering first ClassImplements
+            ]
+        );
+    });
+
     describe("parsing an invalid selector", () => {
         it("throws a useful error", () => {
             const emitter = createEmitter();
 
             emitter.on("Foo >", () => {});
             assert.throws(
-                () => new NodeEventGenerator(emitter),
+                () => new NodeEventGenerator(emitter, STANDARD_ESQUERY_OPTION),
                 /Syntax error in selector "Foo >" at position 5: Expected " ", "!", .*/u
             );
         });
index 6c7252b48f630bf40d591f113bfb6b3187421172..c6dd242e33007e2ac909326559461a86d16ee9d1 100644 (file)
@@ -1028,5 +1028,38 @@ describe("createReportTranslator", () => {
                 "Node must be provided when reporting error if location is not provided"
             );
         });
+
+        it("should throw an error if fix range is invalid", () => {
+            assert.throws(
+                () => translateReport({ node, messageId: "testMessage", fix: () => ({ text: "foo" }) }),
+                "Fix has invalid range"
+            );
+
+            for (const badRange of [[0], [0, null], [null, 0], [void 0, 1], [0, void 0], [void 0, void 0], []]) {
+                assert.throws(
+                    // eslint-disable-next-line no-loop-func
+                    () => translateReport(
+                        { node, messageId: "testMessage", fix: () => ({ range: badRange, text: "foo" }) }
+                    ),
+                    "Fix has invalid range"
+                );
+
+                assert.throws(
+                    // eslint-disable-next-line no-loop-func
+                    () => translateReport(
+                        {
+                            node,
+                            messageId: "testMessage",
+                            fix: () => [
+                                { range: [0, 0], text: "foo" },
+                                { range: badRange, text: "bar" },
+                                { range: [1, 1], text: "baz" }
+                            ]
+                        }
+                    ),
+                    "Fix has invalid range"
+                );
+            }
+        });
     });
 });
index 3f2393621b6b85f439292e363f4aa6694d9dc3f8..3aaf6f91f3f4e44c0f6ddaf18b4bd2e305ddbd84 100644 (file)
@@ -11,8 +11,7 @@ const sinon = require("sinon"),
     EventEmitter = require("events"),
     { RuleTester } = require("../../../lib/rule-tester"),
     assert = require("chai").assert,
-    nodeAssert = require("assert"),
-    { noop } = require("lodash");
+    nodeAssert = require("assert");
 
 const NODE_ASSERT_STRICT_EQUAL_OPERATOR = (() => {
     try {
@@ -23,6 +22,15 @@ const NODE_ASSERT_STRICT_EQUAL_OPERATOR = (() => {
     throw new Error("unexpected successful assertion");
 })();
 
+/**
+ * Do nothing.
+ * @returns {void}
+ */
+function noop() {
+
+    // do nothing.
+}
+
 //------------------------------------------------------------------------------
 // Rewire Things
 //------------------------------------------------------------------------------
index 7a8de4fe5eff6ff36c019d1e2ea52f0d346905bd..0ccb440d39216fac46968e522ba5d2a7c3256348 100644 (file)
@@ -810,6 +810,14 @@ ruleTester.run("arrow-body-style", rule, {
             `,
             options: ["always"],
             errors: [{ messageId: "expectedBlock" }]
+        },
+
+        // https://github.com/eslint/eslint/issues/14633
+        {
+            code: "const createMarker = (color) => ({ latitude, longitude }, index) => {};",
+            output: "const createMarker = (color) => {return ({ latitude, longitude }, index) => {}};",
+            options: ["always"],
+            errors: [{ messageId: "expectedBlock" }]
         }
     ]
 });
index ad35ad09c70d8b175a726ba787ff2b23df46953e..02517ffaeeb1775d05a8161be9a21b85fddc8419 100644 (file)
@@ -2204,6 +2204,26 @@ ruleTester.run("indent", rule, {
             `,
             options: [4, { offsetTernaryExpressions: true }]
         },
+        {
+            code: unIndent`
+              condition1
+                ? condition2
+                  ? Promise.resolve(1)
+                  : Promise.resolve(2)
+                : Promise.resolve(3)
+            `,
+            options: [2, { offsetTernaryExpressions: true }]
+        },
+        {
+            code: unIndent`
+              condition1
+                ? Promise.resolve(1)
+                : condition2
+                  ? Promise.resolve(2)
+                  : Promise.resolve(3)
+            `,
+            options: [2, { offsetTernaryExpressions: true }]
+        },
         {
             code: unIndent`
               condition
@@ -4125,7 +4145,7 @@ ruleTester.run("indent", rule, {
         //----------------------------------------------------------------------
         // JSX tests
         // https://github.com/eslint/eslint/issues/8425
-        // Some of the following tests are adapted from the the tests in eslint-plugin-react.
+        // Some of the following tests are adapted from the tests in eslint-plugin-react.
         // License: https://github.com/yannickcr/eslint-plugin-react/blob/7ca9841f22d599f447a27ef5b2a97def9229d6c8/LICENSE
         //----------------------------------------------------------------------
 
@@ -9825,7 +9845,7 @@ ruleTester.run("indent", rule, {
 
         //----------------------------------------------------------------------
         // JSX tests
-        // Some of the following tests are adapted from the the tests in eslint-plugin-react.
+        // Some of the following tests are adapted from the tests in eslint-plugin-react.
         // License: https://github.com/yannickcr/eslint-plugin-react/blob/7ca9841f22d599f447a27ef5b2a97def9229d6c8/LICENSE
         //----------------------------------------------------------------------
 
index 42d35c8575136b5902e5e498d1dd294fdc36ee55..e200bbc704df3938253d2c241dcb1cae3479f645 100644 (file)
@@ -16,7 +16,7 @@ const rule = require("../../../lib/rules/no-duplicate-imports"),
 // Tests
 //------------------------------------------------------------------------------
 
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6, sourceType: "module" } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 12, sourceType: "module" } });
 
 ruleTester.run("no-duplicate-imports", rule, {
     valid: [
@@ -26,6 +26,9 @@ ruleTester.run("no-duplicate-imports", rule, {
         "import * as Foobar from \"async\";",
         "import \"foo\"",
         "import os from \"os\";\nexport { something } from \"os\";",
+        "import * as bar from \"os\";\nimport { baz } from \"os\";",
+        "import foo, * as bar from \"os\";\nimport { baz } from \"os\";",
+        "import foo, { bar } from \"os\";\nimport * as baz from \"os\";",
         {
             code: "import os from \"os\";\nexport { hello } from \"hello\";",
             options: [{ includeExports: true }]
@@ -45,6 +48,26 @@ ruleTester.run("no-duplicate-imports", rule, {
         {
             code: "import { merge } from \"lodash-es\";\nexport { merge as lodashMerge }",
             options: [{ includeExports: true }]
+        },
+        {
+            code: "export { something } from \"os\";\nexport * as os from \"os\";",
+            options: [{ includeExports: true }]
+        },
+        {
+            code: "import { something } from \"os\";\nexport * as os from \"os\";",
+            options: [{ includeExports: true }]
+        },
+        {
+            code: "import * as os from \"os\";\nexport { something } from \"os\";",
+            options: [{ includeExports: true }]
+        },
+        {
+            code: "import os from \"os\";\nexport * from \"os\";",
+            options: [{ includeExports: true }]
+        },
+        {
+            code: "export { something } from \"os\";\nexport * from \"os\";",
+            options: [{ includeExports: true }]
         }
     ],
     invalid: [
@@ -53,11 +76,22 @@ ruleTester.run("no-duplicate-imports", rule, {
             errors: [{ messageId: "import", data: { module: "fs" }, type: "ImportDeclaration" }]
         },
         {
-            code: "import { merge } from \"lodash-es\";import { find } from \"lodash-es\";",
+            code: "import { merge } from \"lodash-es\";\nimport { find } from \"lodash-es\";",
             errors: [{ messageId: "import", data: { module: "lodash-es" }, type: "ImportDeclaration" }]
         },
         {
-            code: "import { merge } from \"lodash-es\";import _ from \"lodash-es\";",
+            code: "import { merge } from \"lodash-es\";\nimport _ from \"lodash-es\";",
+            errors: [{ messageId: "import", data: { module: "lodash-es" }, type: "ImportDeclaration" }]
+        },
+        {
+            code: "import os from \"os\";\nimport { something } from \"os\";\nimport * as foobar from \"os\";",
+            errors: [
+                { messageId: "import", data: { module: "os" }, type: "ImportDeclaration" },
+                { messageId: "import", data: { module: "os" }, type: "ImportDeclaration" }
+            ]
+        },
+        {
+            code: "import * as modns from \"lodash-es\";\nimport { merge } from \"lodash-es\";\nimport { baz } from \"lodash-es\";",
             errors: [{ messageId: "import", data: { module: "lodash-es" }, type: "ImportDeclaration" }]
         },
         {
@@ -66,7 +100,7 @@ ruleTester.run("no-duplicate-imports", rule, {
             errors: [{ messageId: "export", data: { module: "os" }, type: "ExportNamedDeclaration" }]
         },
         {
-            code: "import os from \"os\"; export { os as foobar } from \"os\";\nexport { something } from \"os\";",
+            code: "import os from \"os\";\nexport { os as foobar } from \"os\";\nexport { something } from \"os\";",
             options: [{ includeExports: true }],
             errors: [
                 { messageId: "exportAs", data: { module: "os" }, type: "ExportNamedDeclaration" },
@@ -80,7 +114,27 @@ ruleTester.run("no-duplicate-imports", rule, {
             errors: [{ messageId: "exportAs", data: { module: "os" }, type: "ExportNamedDeclaration" }]
         },
         {
-            code: "import os from \"os\";\nexport * from \"os\";",
+            code: "import os from \"os\";\nexport * as os from \"os\";",
+            options: [{ includeExports: true }],
+            errors: [{ messageId: "exportAs", data: { module: "os" }, type: "ExportAllDeclaration" }]
+        },
+        {
+            code: "export * as os from \"os\";\nimport os from \"os\";",
+            options: [{ includeExports: true }],
+            errors: [{ messageId: "importAs", data: { module: "os" }, type: "ImportDeclaration" }]
+        },
+        {
+            code: "import * as modns from \"mod\";\nexport * as  modns from \"mod\";",
+            options: [{ includeExports: true }],
+            errors: [{ messageId: "exportAs", data: { module: "mod" }, type: "ExportAllDeclaration" }]
+        },
+        {
+            code: "export * from \"os\";\nexport * from \"os\";",
+            options: [{ includeExports: true }],
+            errors: [{ messageId: "export", data: { module: "os" }, type: "ExportAllDeclaration" }]
+        },
+        {
+            code: "import \"os\";\nexport * from \"os\";",
             options: [{ includeExports: true }],
             errors: [{ messageId: "exportAs", data: { module: "os" }, type: "ExportAllDeclaration" }]
         }
index ca5b430fafe4e7b53a3d03206dd7477c61a09d41..4cb4a64b35c61ee9c93589e3d16b71d77979b467 100644 (file)
@@ -48,12 +48,6 @@ ruleTester.run("no-extend-native", rule, {
         {
             code: "{ let Object = function() {}; Object.prototype.p = 0 }",
             parserOptions: { ecmaVersion: 6 }
-        },
-
-        // TODO(mdjermanovic): This test should become `invalid` in the next major version, when we upgrade the `globals` package.
-        {
-            code: "WeakRef.prototype.p = 0",
-            env: { es2021: true }
         }
     ],
     invalid: [{
@@ -71,6 +65,30 @@ ruleTester.run("no-extend-native", rule, {
             data: { builtin: "BigInt" },
             type: "AssignmentExpression"
         }]
+    }, {
+        code: "WeakRef.prototype.p = 0",
+        env: { es2021: true },
+        errors: [{
+            messageId: "unexpected",
+            data: { builtin: "WeakRef" },
+            type: "AssignmentExpression"
+        }]
+    }, {
+        code: "FinalizationRegistry.prototype.p = 0",
+        env: { es2021: true },
+        errors: [{
+            messageId: "unexpected",
+            data: { builtin: "FinalizationRegistry" },
+            type: "AssignmentExpression"
+        }]
+    }, {
+        code: "AggregateError.prototype.p = 0",
+        env: { es2021: true },
+        errors: [{
+            messageId: "unexpected",
+            data: { builtin: "AggregateError" },
+            type: "AssignmentExpression"
+        }]
     }, {
         code: "Function.prototype['p'] = 0",
         errors: [{
index 43c66c7562ad132ca9991f3943b044d140bef1ef..a14ececc09973edfab622eab6825163aeb3c9253 100644 (file)
@@ -615,10 +615,28 @@ ruleTester.run("no-extra-parens", rule, {
         "for ((let)[a]();;);",
         "for ((let[a]) + b;;);",
 
-        "for ((let) in foo);",
+        // ForInStatement#left expression cannot start with `let[`. It would be parsed as a `let` declaration with array pattern, or a syntax error.
         "for ((let[foo]) in bar);",
         "for ((let)[foo] in bar);",
         "for ((let[foo].bar) in baz);",
+        "for ((let[foo]).bar in baz);",
+        "for ((let)[foo].bar in baz);",
+
+        // ForOfStatement#left expression cannot start with `let`. It's explicitly forbidden by the specification.
+        "for ((let) of foo);",
+        "for ((let).foo of bar);",
+        "for ((let.foo) of bar);",
+        "for ((let[foo]) of bar);",
+        "for ((let)[foo] of bar);",
+        "for ((let.foo.bar) of baz);",
+        "for ((let.foo).bar of baz);",
+        "for ((let).foo.bar of baz);",
+        "for ((let[foo].bar) of baz);",
+        "for ((let[foo]).bar of baz);",
+        "for ((let)[foo].bar of baz);",
+        "for ((let)().foo of bar);",
+        "for ((let()).foo of bar);",
+        "for ((let().foo) of bar);",
 
         // https://github.com/eslint/eslint/issues/11706 (also in invalid[])
         "for (let a = (b in c); ;);",
@@ -1866,6 +1884,18 @@ ruleTester.run("no-extra-parens", rule, {
             "Identifier",
             1
         ),
+        invalid(
+            "for (foo of (baz = bar));",
+            "for (foo of baz = bar);",
+            "AssignmentExpression",
+            1
+        ),
+        invalid(
+            "function* f() { for (foo of (yield bar)); }",
+            "function* f() { for (foo of yield bar); }",
+            "YieldExpression",
+            1
+        ),
         invalid(
             "for (foo of ((bar, baz)));",
             "for (foo of (bar, baz));",
@@ -2055,18 +2085,172 @@ ruleTester.run("no-extra-parens", rule, {
             1
         ),
 
+        // ForInStatement#left expression cannot start with `let[`, but it can start with `let` if it isn't followed by `[`
+        invalid(
+            "for ((let) in foo);",
+            "for (let in foo);",
+            "Identifier",
+            1
+        ),
+        invalid(
+            "for ((let())[a] in foo);",
+            "for (let()[a] in foo);",
+            "CallExpression",
+            1
+        ),
+        invalid(
+            "for ((let.a) in foo);",
+            "for (let.a in foo);",
+            "MemberExpression",
+            1
+        ),
+        invalid(
+            "for ((let).a in foo);",
+            "for (let.a in foo);",
+            "Identifier",
+            1
+        ),
+        invalid(
+            "for ((let).a.b in foo);",
+            "for (let.a.b in foo);",
+            "Identifier",
+            1
+        ),
+        invalid(
+            "for ((let).a[b] in foo);",
+            "for (let.a[b] in foo);",
+            "Identifier",
+            1
+        ),
+        invalid(
+            "for ((let.a)[b] in foo);",
+            "for (let.a[b] in foo);",
+            "MemberExpression",
+            1
+        ),
+        invalid(
+            "for ((let.a[b]) in foo);",
+            "for (let.a[b] in foo);",
+            "MemberExpression",
+            1
+        ),
+        invalid(
+            "for (((let[a])) in foo);",
+            "for ((let[a]) in foo);",
+            "MemberExpression",
+            1
+        ),
+        invalid(
+            "for (((let))[a] in foo);",
+            "for ((let)[a] in foo);",
+            "Identifier",
+            1
+        ),
+        invalid(
+            "for (((let[a])).b in foo);",
+            "for ((let[a]).b in foo);",
+            "MemberExpression",
+            1
+        ),
+        invalid(
+            "for (((let))[a].b in foo);",
+            "for ((let)[a].b in foo);",
+            "Identifier",
+            1
+        ),
+        invalid(
+            "for (((let)[a]).b in foo);",
+            "for ((let)[a].b in foo);",
+            "MemberExpression",
+            1
+        ),
+        invalid(
+            "for (((let[a]).b) in foo);",
+            "for ((let[a]).b in foo);",
+            "MemberExpression",
+            1
+        ),
+        invalid(
+            "for ((Let[a]) in foo);",
+            "for (Let[a] in foo);",
+            "MemberExpression",
+            1
+        ),
+        invalid(
+            "for ((lett)[a] in foo);",
+            "for (lett[a] in foo);",
+            "Identifier",
+            1
+        ),
+
+        // ForOfStatement#left expression cannot start with `let`
+        invalid(
+            "for (((let)) of foo);",
+            "for ((let) of foo);",
+            "Identifier",
+            1
+        ),
         invalid(
-            "for ((let.foo) in bar);",
-            "for (let.foo in bar);",
+            "for (((let)).a of foo);",
+            "for ((let).a of foo);",
+            "Identifier",
+            1
+        ),
+        invalid(
+            "for (((let))[a] of foo);",
+            "for ((let)[a] of foo);",
+            "Identifier",
+            1
+        ),
+        invalid(
+            "for (((let).a) of foo);",
+            "for ((let).a of foo);",
+            "MemberExpression",
+            1
+        ),
+        invalid(
+            "for (((let[a]).b) of foo);",
+            "for ((let[a]).b of foo);",
+            "MemberExpression",
+            1
+        ),
+        invalid(
+            "for (((let).a).b of foo);",
+            "for ((let).a.b of foo);",
+            "MemberExpression",
+            1
+        ),
+        invalid(
+            "for (((let).a.b) of foo);",
+            "for ((let).a.b of foo);",
             "MemberExpression",
             1
         ),
         invalid(
-            "for ((let).foo.bar in baz);",
-            "for (let.foo.bar in baz);",
+            "for (((let.a).b) of foo);",
+            "for ((let.a).b of foo);",
+            "MemberExpression",
+            1
+        ),
+        invalid(
+            "for (((let()).a) of foo);",
+            "for ((let()).a of foo);",
+            "MemberExpression",
+            1
+        ),
+        invalid(
+            "for ((Let) of foo);",
+            "for (Let of foo);",
+            "Identifier",
+            1
+        ),
+        invalid(
+            "for ((lett) of foo);",
+            "for (lett of foo);",
             "Identifier",
             1
         ),
+
         invalid("for (a in (b, c));", "for (a in b, c);", "SequenceExpression", null),
         invalid(
             "(let)",
index fa2b68b4975977a3bcc8555180518ee6df12b332..f7ca9dcff3facc0238e1266d7ad4657fa9a8fa3b 100644 (file)
@@ -88,7 +88,23 @@ ruleTester.run("no-implicit-coercion", rule, {
         { code: "`${foo}` + ''", parserOptions: { ecmaVersion: 6 } },
         "foo += 'bar'",
         { code: "foo += `${bar}`", parserOptions: { ecmaVersion: 6 } },
-        "+42"
+        { code: "`a${foo}`", options: [{ disallowTemplateShorthand: true }], parserOptions: { ecmaVersion: 6 } },
+        { code: "`${foo}b`", options: [{ disallowTemplateShorthand: true }], parserOptions: { ecmaVersion: 6 } },
+        { code: "`${foo}${bar}`", options: [{ disallowTemplateShorthand: true }], parserOptions: { ecmaVersion: 6 } },
+        { code: "tag`${foo}`", options: [{ disallowTemplateShorthand: true }], parserOptions: { ecmaVersion: 6 } },
+        { code: "`${foo}`", parserOptions: { ecmaVersion: 6 } },
+        { code: "`${foo}`", options: [{ }], parserOptions: { ecmaVersion: 6 } },
+        { code: "`${foo}`", options: [{ disallowTemplateShorthand: false }], parserOptions: { ecmaVersion: 6 } },
+        "+42",
+
+        // https://github.com/eslint/eslint/issues/14623
+        "'' + String(foo)",
+        "String(foo) + ''",
+        { code: "`` + String(foo)", parserOptions: { ecmaVersion: 6 } },
+        { code: "String(foo) + ``", parserOptions: { ecmaVersion: 6 } },
+        { code: "`${'foo'}`", options: [{ disallowTemplateShorthand: true }], parserOptions: { ecmaVersion: 6 } },
+        { code: "`${`foo`}`", options: [{ disallowTemplateShorthand: true }], parserOptions: { ecmaVersion: 6 } },
+        { code: "`${String(foo)}`", options: [{ disallowTemplateShorthand: true }], parserOptions: { ecmaVersion: 6 } }
     ],
     invalid: [
         {
@@ -248,6 +264,39 @@ ruleTester.run("no-implicit-coercion", rule, {
                 type: "BinaryExpression"
             }]
         },
+        {
+            code: "`${foo}`",
+            output: "String(foo)",
+            options: [{ disallowTemplateShorthand: true }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{
+                messageId: "useRecommendation",
+                data: { recommendation: "String(foo)" },
+                type: "TemplateLiteral"
+            }]
+        },
+        {
+            code: "`\\\n${foo}`",
+            output: "String(foo)",
+            options: [{ disallowTemplateShorthand: true }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{
+                messageId: "useRecommendation",
+                data: { recommendation: "String(foo)" },
+                type: "TemplateLiteral"
+            }]
+        },
+        {
+            code: "`${foo}\\\n`",
+            output: "String(foo)",
+            options: [{ disallowTemplateShorthand: true }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{
+                messageId: "useRecommendation",
+                data: { recommendation: "String(foo)" },
+                type: "TemplateLiteral"
+            }]
+        },
         {
             code: "foo += \"\"",
             output: "foo = String(foo)",
index 485ce1e41fd3d599fa47dc4bb4721f14ad2b50c5..6484db816db5cdc1781b66f377acaaa2c28da7ea 100644 (file)
@@ -39,6 +39,20 @@ ruleTester.run("no-invalid-regexp", rule, {
         "new RegExp('(?<a>b)\\k<a>', 'u')",
         "new RegExp('\\\\p{Letter}', 'u')",
 
+        // unknown flags
+        "RegExp('{', flags)", // valid without the "u" flag
+        "new RegExp('{', flags)", // valid without the "u" flag
+        "RegExp('\\\\u{0}*', flags)", // valid with the "u" flag
+        "new RegExp('\\\\u{0}*', flags)", // valid with the "u" flag
+        {
+            code: "RegExp('{', flags)", // valid without the "u" flag
+            options: [{ allowConstructorFlags: ["u"] }]
+        },
+        {
+            code: "RegExp('\\\\u{0}*', flags)", // valid with the "u" flag
+            options: [{ allowConstructorFlags: ["a"] }]
+        },
+
         // ES2020
         "new RegExp('(?<\\\\ud835\\\\udc9c>.)', 'g')",
         "new RegExp('(?<\\\\u{1d49c}>.)', 'g')",
@@ -165,6 +179,48 @@ ruleTester.run("no-invalid-regexp", rule, {
                 type: "NewExpression"
             }]
         },
+        {
+            code: String.raw`RegExp('\\u{0}*');`,
+            errors: [{
+                messageId: "regexMessage",
+                data: { message: "Invalid regular expression: /\\u{0}*/: Nothing to repeat" },
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: String.raw`new RegExp('\\u{0}*');`,
+            errors: [{
+                messageId: "regexMessage",
+                data: { message: "Invalid regular expression: /\\u{0}*/: Nothing to repeat" },
+                type: "NewExpression"
+            }]
+        },
+        {
+            code: String.raw`new RegExp('\\u{0}*', '');`,
+            errors: [{
+                messageId: "regexMessage",
+                data: { message: "Invalid regular expression: /\\u{0}*/: Nothing to repeat" },
+                type: "NewExpression"
+            }]
+        },
+        {
+            code: String.raw`new RegExp('\\u{0}*', 'a');`,
+            options: [{ allowConstructorFlags: ["a"] }],
+            errors: [{
+                messageId: "regexMessage",
+                data: { message: "Invalid regular expression: /\\u{0}*/: Nothing to repeat" },
+                type: "NewExpression"
+            }]
+        },
+        {
+            code: String.raw`RegExp('\\u{0}*');`,
+            options: [{ allowConstructorFlags: ["a"] }],
+            errors: [{
+                messageId: "regexMessage",
+                data: { message: "Invalid regular expression: /\\u{0}*/: Nothing to repeat" },
+                type: "CallExpression"
+            }]
+        },
 
         // https://github.com/eslint/eslint/issues/10861
         {
index 6e1b757a71237eae7d41cc34fbac06bada2c27f0..3b19d63696f382ec3a2570d15c53525ee00804d2 100644 (file)
@@ -9,7 +9,8 @@
 // Requirements
 //------------------------------------------------------------------------------
 
-const lodash = require("lodash");
+const merge = require("lodash.merge");
+
 const rule = require("../../../lib/rules/no-invalid-this");
 const { RuleTester } = require("../../../lib/rule-tester");
 
@@ -69,7 +70,7 @@ function extractPatterns(patterns, type) {
 
     // Clone and apply the pattern environment.
     const patternsList = patterns.map(pattern => pattern[type].map(applyCondition => {
-        const thisPattern = lodash.cloneDeep(pattern);
+        const thisPattern = merge({}, pattern);
 
         applyCondition(thisPattern);
 
@@ -79,7 +80,10 @@ function extractPatterns(patterns, type) {
             thisPattern.code += " /* should error */";
         }
 
-        return lodash.omit(thisPattern, ["valid", "invalid"]);
+        delete thisPattern.valid;
+        delete thisPattern.invalid;
+
+        return thisPattern;
     }));
 
     // Flatten.
index 3650cd09fce397c0ad9515afd56e3bdb53eebada..782af07e31031bd2d8e2e2f1b2904f1fec589087 100644 (file)
@@ -52,13 +52,29 @@ ruleTester.run("no-mixed-operators", rule, {
             code: "(a || b) ? c : d",
             options: [{ groups: [["&&", "||", "?:"]] }]
         },
+        {
+            code: "a ? (b || c) : d",
+            options: [{ groups: [["&&", "||", "?:"]] }]
+        },
+        {
+            code: "a ? b : (c || d)",
+            options: [{ groups: [["&&", "||", "?:"]] }]
+        },
         {
             code: "a || (b ? c : d)",
             options: [{ groups: [["&&", "||", "?:"]] }]
         },
+        {
+            code: "(a ? b : c) || d",
+            options: [{ groups: [["&&", "||", "?:"]] }]
+        },
         "a || (b ? c : d)",
         "(a || b) ? c : d",
-        "a || b ? c : d"
+        "a || b ? c : d",
+        "a ? (b || c) : d",
+        "a ? b || c : d",
+        "a ? b : (c || d)",
+        "a ? b : c || d"
     ],
     invalid: [
         {
index dc907465c60b47f488fc90cc15a5700c6042ba75..c534f55b33f95e607b489ae6108b93e17359b8d2 100644 (file)
@@ -51,7 +51,9 @@ ruleTester.run("no-mutli-assign", rule, {
         { code: "for(let a = 0, b = 0;;){}", parserOptions: { ecmaVersion: 6 } },
         { code: "for(const a = 0, b = 0;;){}", parserOptions: { ecmaVersion: 6 } },
         { code: "export let a, b;", parserOptions: { ecmaVersion: 6, sourceType: "module" } },
-        { code: "export let a,\n b = 0;", 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 } }
     ],
 
     invalid: [
@@ -137,6 +139,39 @@ ruleTester.run("no-mutli-assign", rule, {
             errors: [
                 errorAt(1, 5, "AssignmentExpression")
             ]
+        },
+        {
+            code: "const x = {};\nconst y = x.one = 1;",
+            options: [{ ignoreNonDeclaration: true }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                errorAt(2, 11, "AssignmentExpression")
+            ]
+
+        },
+        {
+            code: "let a, b;a = b = 1",
+            options: [{}],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                errorAt(1, 14, "AssignmentExpression")
+            ]
+        },
+        {
+            code: "let x, y;x = y = 'baz'",
+            options: [{ ignoreNonDeclaration: false }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                errorAt(1, 14, "AssignmentExpression")
+            ]
+        },
+        {
+            code: "const a = b = 1",
+            options: [{ ignoreNonDeclaration: true }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                errorAt(1, 11, "AssignmentExpression")
+            ]
         }
     ]
 });
index f6293b7e1521d1a09db547ef6cf610af82854426..f89d6853fa3197886b3a40e0b227bf9675ff4f0f 100644 (file)
@@ -9,7 +9,6 @@
 // Requirements
 //------------------------------------------------------------------------------
 
-const path = require("path");
 const rule = require("../../../lib/rules/no-redeclare");
 const { RuleTester } = require("../../../lib/rule-tester");
 
@@ -17,7 +16,6 @@ const { RuleTester } = require("../../../lib/rule-tester");
 // Tests
 //------------------------------------------------------------------------------
 
-const looseParserPath = path.resolve(__dirname, "../../tools/loose-parser.js");
 const ruleTester = new RuleTester();
 
 ruleTester.run("no-redeclare", rule, {
@@ -205,112 +203,6 @@ ruleTester.run("no-redeclare", rule, {
             ]
         },
 
-        // let/const
-        {
-            code: "let a; let a;",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015 },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-        {
-            code: "let a; let a;",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015, sourceType: "module" },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-        {
-            code: "let a; let a;",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015, ecmaFeatures: { globalReturn: true } },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-        {
-            code: "let a; const a = 0;",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015 },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-        {
-            code: "const a = 0; const a = 0;",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015 },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-        {
-            code: "if (test) { let a; let a; }",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015 },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-        {
-            code: "switch (test) { case 0: let a; let a; }",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015 },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-        {
-            code: "for (let a, a;;);",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015 },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-        {
-            code: "for (let [a, a] in xs);",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015 },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-        {
-            code: "for (let [a, a] of xs);",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015 },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-        {
-            code: "function f() { let a; let a; }",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015 },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-        {
-            code: "function f(a) { let a; }",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015 },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-        {
-            code: "function f() { if (test) { let a; let a; } }",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015 },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-
         // Comments and built-ins.
         {
             code: "/*globals Array */",
index 31458f15fc1c877fce091cdaab45dc6179f42219..ac0602a25b29f43215eaec3b932e04cd9edbe24b 100644 (file)
@@ -58,7 +58,7 @@ ruleTester.run("no-restricted-exports", rule, {
         { code: "export { a } from 'foo';", options: [{ restrictedNamedExports: ["x"] }] },
         { code: "export { b as a } from 'foo';", options: [{ restrictedNamedExports: ["x"] }] },
 
-        // does not mistakenly disallow non-exported identifers that appear in named export declarations
+        // does not mistakenly disallow non-exported identifiers that appear in named export declarations
         { code: "export var b = a;", options: [{ restrictedNamedExports: ["a"] }] },
         { code: "export let [b = a] = [];", options: [{ restrictedNamedExports: ["a"] }] },
         { code: "export const [b] = [a];", options: [{ restrictedNamedExports: ["a"] }] },
index 363229712c99dd649be5af724325f5c3145fa287..f54d4df03540a102c1831ee87889844c12fb45f8 100644 (file)
@@ -37,6 +37,10 @@ ruleTester.run("no-restricted-imports", rule, {
             code: "import withGitignores from \"foo/bar\";",
             options: [{ patterns: ["foo/*", "!foo/bar"] }]
         },
+        {
+            code: "import withPatterns from \"foo/bar\";",
+            options: [{ patterns: [{ group: ["foo/*", "!foo/bar"], message: "foo is forbidden, use bar instead" }] }]
+        },
         {
             code: "import AllowedObject from \"foo\";",
             options: [{
@@ -241,6 +245,36 @@ ruleTester.run("no-restricted-imports", rule, {
             column: 1,
             endColumn: 36
         }]
+    }, {
+        code: "import withPatterns from \"foo/baz\";",
+        options: [{ patterns: [{ group: ["foo/*", "!foo/bar"], message: "foo is forbidden, use foo/bar instead" }] }],
+        errors: [{
+            message: "'foo/baz' import is restricted from being used by a pattern. foo is forbidden, use foo/bar instead",
+            type: "ImportDeclaration",
+            line: 1,
+            column: 1,
+            endColumn: 36
+        }]
+    }, {
+        code: "import withPatterns from \"foo/baz\";",
+        options: [{ patterns: [{ group: ["foo/bar", "foo/baz"], message: "some foo subimports are restricted" }] }],
+        errors: [{
+            message: "'foo/baz' import is restricted from being used by a pattern. some foo subimports are restricted",
+            type: "ImportDeclaration",
+            line: 1,
+            column: 1,
+            endColumn: 36
+        }]
+    }, {
+        code: "import withPatterns from \"foo/bar\";",
+        options: [{ patterns: [{ group: ["foo/bar"] }] }],
+        errors: [{
+            message: "'foo/bar' import is restricted from being used by a pattern.",
+            type: "ImportDeclaration",
+            line: 1,
+            column: 1,
+            endColumn: 36
+        }]
     }, {
         code: "import withGitignores from \"foo/bar\";",
         options: [{ patterns: ["foo/*", "!foo/baz"] }],
index cf8bc41236644d7c46433c5e154a87af085cc949..d5fb36aa4725ad1e43f3e6b4f60647a5ad0eae44 100644 (file)
@@ -145,18 +145,26 @@ ruleTester.run("no-restricted-syntax", rule, {
                 { messageId: "restrictedSyntax", data: { message: "Using '[optional=true]' is not allowed." }, type: "CallExpression" },
                 { messageId: "restrictedSyntax", data: { message: "Using '[optional=true]' is not allowed." }, type: "MemberExpression" }
             ]
-        }
+        },
+
+        // fix https://github.com/estools/esquery/issues/110
+        {
+            code: "a?.b",
+            options: [":nth-child(1)"],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [
+                { messageId: "restrictedSyntax", data: { message: "Using ':nth-child(1)' is not allowed." }, type: "ExpressionStatement" }
+            ]
+        },
 
-        /*
-         * TODO(mysticatea): fix https://github.com/estools/esquery/issues/110
-         * {
-         *     code: "a?.b",
-         *     options: [":nth-child(1)"],
-         *     parserOptions: { ecmaVersion: 2020 },
-         *     errors: [
-         *         { messageId: "restrictedSyntax", data: { message: "Using ':nth-child(1)' is not allowed." }, type: "ExpressionStatement" }
-         *     ]
-         * }
-         */
+        // https://github.com/eslint/eslint/issues/13639#issuecomment-683976062
+        {
+            code: "const foo = [<div/>, <div/>]",
+            options: ["* ~ *"],
+            parserOptions: { ecmaVersion: 2020, ecmaFeatures: { jsx: true } },
+            errors: [
+                { messageId: "restrictedSyntax", data: { message: "Using '* ~ *' is not allowed." }, type: "JSXElement" }
+            ]
+        }
     ]
 });
index ae988ee3dae716d73a7c98374341e37b429ef070..1ac7c4a6c6e3eeea6cce9f0b2172ee98ce219129 100644 (file)
@@ -46,10 +46,24 @@ ruleTester.run("no-sequences", rule, {
         "do {} while ((doSomething(), !!test));",
         "for ((doSomething(), somethingElse()); (doSomething(), !!test); );",
         "if ((doSomething(), !!test));",
-        "switch ((doSomething(), !!test)) {}",
+        "switch ((doSomething(), val)) {}",
         "while ((doSomething(), !!test));",
         "with ((doSomething(), val)) {}",
-        { code: "a => ((doSomething(), a))", env: { es6: true } }
+        { code: "a => ((doSomething(), a))", env: { es6: true } },
+
+        // options object without "allowInParentheses" property
+        { code: "var foo = (1, 2);", options: [{}] },
+
+        // explicitly set option "allowInParentheses" to default value
+        { code: "var foo = (1, 2);", options: [{ allowInParentheses: true }] },
+
+        // valid code with "allowInParentheses" set to `false`
+        { code: "for ((i = 0, j = 0); test; );", options: [{ allowInParentheses: false }] },
+        { code: "for (; test; (i++, j++));", options: [{ allowInParentheses: false }] },
+
+        // https://github.com/eslint/eslint/issues/14572
+        { code: "const foo = () => { return ((bar = 123), 10) }", env: { es6: true } },
+        { code: "const foo = () => (((bar = 123), 10));", env: { es6: true } }
     ],
 
     // Examples of code that should trigger the rule
@@ -75,6 +89,18 @@ ruleTester.run("no-sequences", rule, {
         { code: "a => (doSomething(), a)", env: { es6: true }, errors: errors(20) },
         { code: "(1), 2", errors: errors(4) },
         { code: "((1)) , (2)", errors: errors(7) },
-        { code: "while((1) , 2);", errors: errors(11) }
+        { code: "while((1) , 2);", errors: errors(11) },
+
+        // option "allowInParentheses": do not allow sequence in parentheses
+        { code: "var foo = (1, 2);", options: [{ allowInParentheses: false }], errors: errors(13) },
+        { code: "(0,eval)(\"foo()\");", options: [{ allowInParentheses: false }], errors: errors(3) },
+        { code: "foo(a, (b, c), d);", options: [{ allowInParentheses: false }], errors: errors(10) },
+        { code: "do {} while ((doSomething(), !!test));", options: [{ allowInParentheses: false }], errors: errors(28) },
+        { code: "for (; (doSomething(), !!test); );", options: [{ allowInParentheses: false }], errors: errors(22) },
+        { code: "if ((doSomething(), !!test));", options: [{ allowInParentheses: false }], errors: errors(19) },
+        { code: "switch ((doSomething(), val)) {}", options: [{ allowInParentheses: false }], errors: errors(23) },
+        { code: "while ((doSomething(), !!test));", options: [{ allowInParentheses: false }], errors: errors(22) },
+        { code: "with ((doSomething(), val)) {}", options: [{ allowInParentheses: false }], errors: errors(21) },
+        { code: "a => ((doSomething(), a))", options: [{ allowInParentheses: false }], env: { es6: true }, errors: errors(21) }
     ]
 });
index 365b2f5d5d39d459f68721c1c6169919ef1e5a00..dc2cc63c4dea035a593ee84b80ce38e846f01088 100644 (file)
@@ -64,7 +64,11 @@ ruleTester.run("no-shadow", rule, {
             code: "function a(x) { var b = function c() { var x = 'foo'; }; }",
             errors: [{
                 messageId: "noShadow",
-                data: { name: "x" },
+                data: {
+                    name: "x",
+                    shadowedLine: 1,
+                    shadowedColumn: 12
+                },
                 type: "Identifier",
                 line: 1,
                 column: 44
@@ -75,7 +79,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "x" },
+                data: {
+                    name: "x",
+                    shadowedLine: 1,
+                    shadowedColumn: 10
+                },
                 type: "Identifier",
                 line: 1,
                 column: 38
@@ -85,7 +93,11 @@ ruleTester.run("no-shadow", rule, {
             code: "function a(x) { var b = function () { var x = 'foo'; }; }",
             errors: [{
                 messageId: "noShadow",
-                data: { name: "x" },
+                data: {
+                    name: "x",
+                    shadowedLine: 1,
+                    shadowedColumn: 12
+                },
                 type: "Identifier",
                 line: 1,
                 column: 43
@@ -95,7 +107,11 @@ ruleTester.run("no-shadow", rule, {
             code: "var x = 1; function a(x) { return ++x; }",
             errors: [{
                 messageId: "noShadow",
-                data: { name: "x" },
+                data: {
+                    name: "x",
+                    shadowedLine: 1,
+                    shadowedColumn: 5
+                },
                 type: "Identifier",
                 line: 1,
                 column: 23
@@ -105,7 +121,11 @@ ruleTester.run("no-shadow", rule, {
             code: "var a=3; function b() { var a=10; }",
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 5
+                },
                 type: "Identifier"
             }]
         },
@@ -113,7 +133,11 @@ ruleTester.run("no-shadow", rule, {
             code: "var a=3; function b() { var a=10; }; setTimeout(function() { b(); }, 0);",
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 5
+                },
                 type: "Identifier"
             }]
         },
@@ -122,11 +146,19 @@ ruleTester.run("no-shadow", rule, {
             errors: [
                 {
                     messageId: "noShadow",
-                    data: { name: "a" },
+                    data: {
+                        name: "a",
+                        shadowedLine: 1,
+                        shadowedColumn: 5
+                    },
                     type: "Identifier"
                 }, {
                     messageId: "noShadow",
-                    data: { name: "b" },
+                    data: {
+                        name: "b",
+                        shadowedLine: 1,
+                        shadowedColumn: 19
+                    },
                     type: "Identifier"
                 }
             ]
@@ -136,7 +168,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "x" },
+                data: {
+                    name: "x",
+                    shadowedLine: 1,
+                    shadowedColumn: 5
+                },
                 type: "Identifier"
             }]
         },
@@ -145,7 +181,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "x" },
+                data: {
+                    name: "x",
+                    shadowedLine: 1,
+                    shadowedColumn: 5
+                },
                 type: "Identifier"
             }]
         },
@@ -154,7 +194,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 21
+                },
                 type: "Identifier"
             }]
         },
@@ -163,7 +207,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 27
+                },
                 type: "Identifier"
             }]
         },
@@ -172,7 +220,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 36
+                },
                 type: "Identifier"
             }]
         },
@@ -181,7 +233,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 36
+                },
                 type: "Identifier"
             }]
         },
@@ -190,7 +246,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 30
+                },
                 type: "Identifier"
             }]
         },
@@ -200,7 +260,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 16
+                },
                 type: "Identifier"
             }]
         },
@@ -210,7 +274,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 16
+                },
                 type: "Identifier"
             }]
         },
@@ -220,7 +288,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 21
+                },
                 type: "Identifier"
             }]
         },
@@ -230,7 +302,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 24
+                },
                 type: "Identifier"
             }]
         },
@@ -240,7 +316,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 22
+                },
                 type: "Identifier"
             }]
         },
@@ -250,7 +330,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 27
+                },
                 type: "Identifier"
             }]
         },
@@ -260,7 +344,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 31
+                },
                 type: "Identifier"
             }]
         },
@@ -270,7 +358,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 31
+                },
                 type: "Identifier"
             }]
         },
@@ -280,7 +372,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 36
+                },
                 type: "Identifier"
             }]
         },
@@ -290,7 +386,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 31
+                },
                 type: "Identifier"
             }]
         },
@@ -300,7 +400,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 31
+                },
                 type: "Identifier"
             }]
         },
@@ -310,7 +414,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 36
+                },
                 type: "Identifier"
             }]
         },
@@ -320,7 +428,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 25
+                },
                 type: "Identifier"
             }]
         },
@@ -330,7 +442,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 25
+                },
                 type: "Identifier"
             }]
         },
@@ -340,7 +456,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 30
+                },
                 type: "Identifier"
             }]
         },
@@ -348,7 +468,11 @@ ruleTester.run("no-shadow", rule, {
             code: "(function a() { function a(){} })()",
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 11
+                },
                 type: "Identifier"
             }]
         },
@@ -357,7 +481,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 11
+                },
                 type: "Identifier"
             }]
         },
@@ -365,7 +493,11 @@ ruleTester.run("no-shadow", rule, {
             code: "(function a() { (function a(){}); })()",
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 11
+                },
                 type: "Identifier"
             }]
         },
@@ -374,7 +506,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 11
+                },
                 type: "Identifier"
             }]
         },
@@ -382,7 +518,11 @@ ruleTester.run("no-shadow", rule, {
             code: "(function() { var a = function(a) {}; })()",
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 19
+                },
                 type: "Identifier"
             }]
         },
@@ -390,7 +530,11 @@ ruleTester.run("no-shadow", rule, {
             code: "(function() { var a = function() { function a() {} }; })()",
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 19
+                },
                 type: "Identifier"
             }]
         },
@@ -399,7 +543,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 19
+                },
                 type: "Identifier"
             }]
         },
@@ -407,7 +555,11 @@ ruleTester.run("no-shadow", rule, {
             code: "(function() { var a = function() { (function a() {}); }; })()",
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 19
+                },
                 type: "Identifier"
             }]
         },
@@ -416,7 +568,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 19
+                },
                 type: "Identifier"
             }]
         },
@@ -425,7 +581,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "a" },
+                data: {
+                    name: "a",
+                    shadowedLine: 1,
+                    shadowedColumn: 19
+                },
                 type: "Identifier"
             }]
         },
@@ -434,7 +594,11 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "noShadow",
-                data: { name: "A" },
+                data: {
+                    name: "A",
+                    shadowedLine: 1,
+                    shadowedColumn: 7
+                },
                 type: "Identifier"
             }]
         },
@@ -443,14 +607,22 @@ ruleTester.run("no-shadow", rule, {
             errors: [
                 {
                     messageId: "noShadow",
-                    data: { name: "a" },
+                    data: {
+                        name: "a",
+                        shadowedLine: 1,
+                        shadowedColumn: 11
+                    },
                     type: "Identifier",
                     line: 1,
                     column: 26
                 },
                 {
                     messageId: "noShadow",
-                    data: { name: "a" },
+                    data: {
+                        name: "a",
+                        shadowedLine: 1,
+                        shadowedColumn: 26
+                    },
                     type: "Identifier",
                     line: 1,
                     column: 40
@@ -461,8 +633,10 @@ ruleTester.run("no-shadow", rule, {
             code: "function foo() { var Object = 0; }",
             options: [{ builtinGlobals: true }],
             errors: [{
-                messageId: "noShadow",
-                data: { name: "Object" },
+                messageId: "noShadowGlobal",
+                data: {
+                    name: "Object"
+                },
                 type: "Identifier"
             }]
         },
@@ -471,8 +645,10 @@ ruleTester.run("no-shadow", rule, {
             options: [{ builtinGlobals: true }],
             env: { browser: true },
             errors: [{
-                messageId: "noShadow",
-                data: { name: "top" },
+                messageId: "noShadowGlobal",
+                data: {
+                    name: "top"
+                },
                 type: "Identifier"
             }]
         },
@@ -481,8 +657,10 @@ ruleTester.run("no-shadow", rule, {
             options: [{ builtinGlobals: true }],
             parserOptions: { ecmaVersion: 6, sourceType: "module" },
             errors: [{
-                messageId: "noShadow",
-                data: { name: "Object" },
+                messageId: "noShadowGlobal",
+                data: {
+                    name: "Object"
+                },
                 type: "Identifier"
             }]
         },
@@ -492,8 +670,10 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaVersion: 6, sourceType: "module" },
             env: { browser: true },
             errors: [{
-                messageId: "noShadow",
-                data: { name: "top" },
+                messageId: "noShadowGlobal",
+                data: {
+                    name: "top"
+                },
                 type: "Identifier"
             }]
         },
@@ -502,8 +682,10 @@ ruleTester.run("no-shadow", rule, {
             options: [{ builtinGlobals: true }],
             parserOptions: { ecmaFeatures: { globalReturn: true } },
             errors: [{
-                messageId: "noShadow",
-                data: { name: "Object" },
+                messageId: "noShadowGlobal",
+                data: {
+                    name: "Object"
+                },
                 type: "Identifier"
             }]
         },
@@ -513,8 +695,10 @@ ruleTester.run("no-shadow", rule, {
             parserOptions: { ecmaFeatures: { globalReturn: true } },
             env: { browser: true },
             errors: [{
-                messageId: "noShadow",
-                data: { name: "top" },
+                messageId: "noShadowGlobal",
+                data: {
+                    name: "top"
+                },
                 type: "Identifier"
             }]
         },
@@ -522,7 +706,11 @@ ruleTester.run("no-shadow", rule, {
             code: "function foo(cb) { (function (cb) { cb(42); })(cb); }",
             errors: [{
                 messageId: "noShadow",
-                data: { name: "cb" },
+                data: {
+                    name: "cb",
+                    shadowedLine: 1,
+                    shadowedColumn: 14
+                },
                 type: "Identifier",
                 line: 1,
                 column: 31
index 1674629c90cbf958061b4147dd4a79e8df429b2d..6f49a0a24fba523a69185ea53c30da5c86ed4cdf 100644 (file)
@@ -82,6 +82,30 @@ ruleTester.run("no-unused-expressions", rule, {
         {
             code: "obj?.foo(\"bar\")",
             parserOptions: { ecmaVersion: 11 }
+        },
+
+        // JSX
+        {
+            code: "<div />",
+            parserOptions: { ecmaFeatures: { jsx: true } }
+        },
+        {
+            code: "<></>",
+            parserOptions: { ecmaFeatures: { jsx: true } }
+        },
+        {
+            code: "var partial = <div />",
+            parserOptions: { ecmaFeatures: { jsx: true } }
+        },
+        {
+            code: "var partial = <div />",
+            options: [{ enforceForJSX: true }],
+            parserOptions: { ecmaFeatures: { jsx: true } }
+        },
+        {
+            code: "var partial = <></>",
+            options: [{ enforceForJSX: true }],
+            parserOptions: { ecmaFeatures: { jsx: true } }
         }
     ],
     invalid: [
@@ -152,6 +176,20 @@ ruleTester.run("no-unused-expressions", rule, {
             code: "obj?.foo().bar",
             parserOptions: { ecmaVersion: 2020 },
             errors: [{ messageId: "unusedExpression", type: "ExpressionStatement" }]
+        },
+
+        // JSX
+        {
+            code: "<div />",
+            options: [{ enforceForJSX: true }],
+            parserOptions: { ecmaFeatures: { jsx: true } },
+            errors: [{ messageId: "unusedExpression", type: "ExpressionStatement" }]
+        },
+        {
+            code: "<></>",
+            options: [{ enforceForJSX: true }],
+            parserOptions: { ecmaFeatures: { jsx: true } },
+            errors: [{ messageId: "unusedExpression", type: "ExpressionStatement" }]
         }
     ]
 });
index 061ac08d51f4b7c51131bc35a17d81bdfe828d99..48ccdb1d42ff9ad8517435062a1dfd7e41b7633d 100644 (file)
@@ -177,6 +177,10 @@ ruleTester.run("no-unused-vars", rule, {
         { code: "(function(obj) { for ( const name in obj ) { return true } })({})", parserOptions: { ecmaVersion: 6 } },
         { code: "(function(obj) { for ( const name in obj ) return true })({})", parserOptions: { ecmaVersion: 6 } },
 
+        // 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 } },
+
         // caughtErrors
         {
             code: "try{}catch(err){console.error(err);}",
@@ -291,6 +295,13 @@ ruleTester.run("no-unused-vars", rule, {
             parserOptions: { ecmaVersion: 2018 }
         },
 
+        // https://github.com/eslint/eslint/issues/14163
+        {
+            code: "let foo, rest;\n({ foo, ...rest } = something);\nconsole.log(rest);",
+            options: [{ ignoreRestSiblings: true }],
+            parserOptions: { ecmaVersion: 2020 }
+        },
+
         // https://github.com/eslint/eslint/issues/10952
         "/*eslint use-every-a:1*/ !function(b, a) { return 1 }",
 
@@ -588,6 +599,23 @@ ruleTester.run("no-unused-vars", rule, {
                 }
             ]
         },
+        {
+            code: "let type, coords;\n({ type, ...coords } = data);\n console.log(type)",
+            options: [{ ignoreRestSiblings: true }],
+            parserOptions: { ecmaVersion: 2018 },
+            errors: [
+                {
+                    line: 2,
+                    column: 13,
+                    messageId: "unusedVar",
+                    data: {
+                        varName: "coords",
+                        action: "assigned a value",
+                        additional: ""
+                    }
+                }
+            ]
+        },
 
         // Unused rest property without ignoreRestSiblings
         {
@@ -971,6 +999,71 @@ ruleTester.run("no-unused-vars", rule, {
                 definedError("c")
             ]
         },
+
+        // https://github.com/eslint/eslint/issues/14325
+        {
+            code: `let x = 0;
+            x++, x = 0;`,
+            parserOptions: { ecmaVersion: 2015 },
+            errors: [{ ...assignedError("x"), line: 2, column: 18 }]
+        },
+        {
+            code: `let x = 0;
+            x++, x = 0;
+            x=3;`,
+            parserOptions: { ecmaVersion: 2015 },
+            errors: [{ ...assignedError("x"), line: 3, column: 13 }]
+        },
+        {
+            code: "let x = 0; x++, 0;",
+            parserOptions: { ecmaVersion: 2015 },
+            errors: [{ ...assignedError("x"), line: 1, column: 12 }]
+        },
+        {
+            code: "let x = 0; 0, x++;",
+            parserOptions: { ecmaVersion: 2015 },
+            errors: [{ ...assignedError("x"), line: 1, column: 15 }]
+        },
+        {
+            code: "let x = 0; 0, (1, x++);",
+            parserOptions: { ecmaVersion: 2015 },
+            errors: [{ ...assignedError("x"), line: 1, column: 19 }]
+        },
+        {
+            code: "let x = 0; foo = (x++, 0);",
+            parserOptions: { ecmaVersion: 2015 },
+            errors: [{ ...assignedError("x"), line: 1, column: 19 }]
+        },
+        {
+            code: "let x = 0; foo = ((0, x++), 0);",
+            parserOptions: { ecmaVersion: 2015 },
+            errors: [{ ...assignedError("x"), line: 1, column: 23 }]
+        },
+        {
+            code: "let x = 0; x += 1, 0;",
+            parserOptions: { ecmaVersion: 2015 },
+            errors: [{ ...assignedError("x"), line: 1, column: 12 }]
+        },
+        {
+            code: "let x = 0; 0, x += 1;",
+            parserOptions: { ecmaVersion: 2015 },
+            errors: [{ ...assignedError("x"), line: 1, column: 15 }]
+        },
+        {
+            code: "let x = 0; 0, (1, x += 1);",
+            parserOptions: { ecmaVersion: 2015 },
+            errors: [{ ...assignedError("x"), line: 1, column: 19 }]
+        },
+        {
+            code: "let x = 0; foo = (x += 1, 0);",
+            parserOptions: { ecmaVersion: 2015 },
+            errors: [{ ...assignedError("x"), line: 1, column: 19 }]
+        },
+        {
+            code: "let x = 0; foo = ((0, x += 1), 0);",
+            parserOptions: { ecmaVersion: 2015 },
+            errors: [{ ...assignedError("x"), line: 1, column: 23 }]
+        },
         {
             code: "(function ({ a, b }, { c } ) { return b; })();",
             parserOptions: { ecmaVersion: 2015 },
@@ -1038,7 +1131,7 @@ ruleTester.run("no-unused-vars", rule, {
             code: `let myArray = [1,2,3,4].filter((x) => x == 0);
     myArray = myArray.filter((x) => x == 1);`,
             parserOptions: { ecmaVersion: 2015 },
-            errors: [{ ...assignedError("myArray"), line: 2, column: 15 }]
+            errors: [{ ...assignedError("myArray"), line: 2, column: 5 }]
         },
         {
             code: "const a = 1; a += 1;",
@@ -1047,21 +1140,28 @@ ruleTester.run("no-unused-vars", rule, {
         },
         {
             code: "var a = function() { a(); };",
-            errors: [{ ...assignedError("a"), line: 1, column: 22 }]
+            errors: [{ ...assignedError("a"), line: 1, column: 5 }]
         },
         {
             code: "var a = function(){ return function() { a(); } };",
-            errors: [{ ...assignedError("a"), line: 1, column: 41 }]
+            errors: [{ ...assignedError("a"), line: 1, column: 5 }]
         },
         {
             code: "const a = () => { a(); };",
             parserOptions: { ecmaVersion: 2015 },
-            errors: [{ ...assignedError("a"), line: 1, column: 19 }]
+            errors: [{ ...assignedError("a"), line: 1, column: 7 }]
         },
         {
             code: "const a = () => () => { a(); };",
             parserOptions: { ecmaVersion: 2015 },
-            errors: [{ ...assignedError("a"), line: 1, column: 25 }]
+            errors: [{ ...assignedError("a"), line: 1, column: 7 }]
+        },
+
+        // https://github.com/eslint/eslint/issues/14324
+        {
+            code: "let x = [];\nx = x.concat(x);",
+            parserOptions: { ecmaVersion: 2015 },
+            errors: [{ ...assignedError("x"), line: 2, column: 1 }]
         },
         {
 
@@ -1074,7 +1174,25 @@ ruleTester.run("no-unused-vars", rule, {
                 }
             }`,
             parserOptions: { ecmaVersion: 2020 },
-            errors: [{ ...definedError("foo"), line: 3, column: 22 }, { ...assignedError("a"), line: 6, column: 21 }]
+            errors: [{ ...assignedError("a"), line: 2, column: 13 }, { ...definedError("foo"), line: 3, column: 22 }]
+        },
+        {
+            code: `let foo;
+            init();
+            foo = foo + 2;
+            function init() {
+                foo = 1;
+            }`,
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ ...assignedError("foo"), line: 3, column: 13 }]
+        },
+        {
+            code: `function foo(n) {
+                if (n < 2) return 1;
+                return n * foo(n - 1);
+            }`,
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ ...definedError("foo"), line: 1, column: 10 }]
         },
         {
             code: `let c = 'c'
index af8f6b8a52026ffa9c99dc0513d44fbcdfc7948a..f09a9d448cac580e537349067b26398583673b19 100644 (file)
@@ -131,6 +131,26 @@ ruleTester.run("no-useless-rename", rule, {
             output: "let {foo} = obj;",
             errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "foo" } }]
         },
+        {
+            code: "({foo: (foo)} = obj);",
+            output: "({foo} = obj);",
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "foo" } }]
+        },
+        {
+            code: "let {\\u0061: a} = obj;",
+            output: "let {a} = obj;",
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "a" } }]
+        },
+        {
+            code: "let {a: \\u0061} = obj;",
+            output: "let {\\u0061} = obj;",
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "a" } }]
+        },
+        {
+            code: "let {\\u0061: \\u0061} = obj;",
+            output: "let {\\u0061} = obj;",
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "a" } }]
+        },
         {
             code: "let {a, foo: foo} = obj;",
             output: "let {a, foo} = obj;",
@@ -225,6 +245,21 @@ ruleTester.run("no-useless-rename", rule, {
             output: "let {foo: {bar = {}} = {}} = obj;",
             errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "bar" } }]
         },
+        {
+            code: "({foo: (foo) = a} = obj);",
+            output: null, // The rule doesn't autofix this edge case. The correct fix would be without parens: `let {foo = a} = obj;`
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "foo" } }]
+        },
+        {
+            code: "let {foo: foo = (a)} = obj;",
+            output: "let {foo = (a)} = obj;",
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "foo" } }]
+        },
+        {
+            code: "let {foo: foo = (a, b)} = obj;",
+            output: "let {foo = (a, b)} = obj;",
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "foo" } }]
+        },
         {
             code: "function func({foo: foo}) {}",
             output: "function func({foo}) {}",
@@ -341,6 +376,21 @@ ruleTester.run("no-useless-rename", rule, {
             output: "import {foo} from 'foo';",
             errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Import", name: "foo" } }]
         },
+        {
+            code: "import {\\u0061 as a} from 'foo';",
+            output: "import {a} from 'foo';",
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Import", name: "a" } }]
+        },
+        {
+            code: "import {a as \\u0061} from 'foo';",
+            output: "import {\\u0061} from 'foo';",
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Import", name: "a" } }]
+        },
+        {
+            code: "import {\\u0061 as \\u0061} from 'foo';",
+            output: "import {\\u0061} from 'foo';",
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Import", name: "a" } }]
+        },
         {
             code: "import {foo as foo, bar as baz} from 'foo';",
             output: "import {foo, bar as baz} from 'foo';",
@@ -364,6 +414,21 @@ ruleTester.run("no-useless-rename", rule, {
             output: "var foo = 0; export {foo};",
             errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Export", name: "foo" } }]
         },
+        {
+            code: "var a = 0; export {a as \\u0061};",
+            output: "var a = 0; export {a};",
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Export", name: "a" } }]
+        },
+        {
+            code: "var \\u0061 = 0; export {\\u0061 as a};",
+            output: "var \\u0061 = 0; export {\\u0061};",
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Export", name: "a" } }]
+        },
+        {
+            code: "var \\u0061 = 0; export {\\u0061 as \\u0061};",
+            output: "var \\u0061 = 0; export {\\u0061};",
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Export", name: "a" } }]
+        },
         {
             code: "var foo = 0; var bar = 0; export {foo as foo, bar as baz};",
             output: "var foo = 0; var bar = 0; export {foo, bar as baz};",
@@ -387,6 +452,21 @@ ruleTester.run("no-useless-rename", rule, {
             output: "export {foo} from 'foo';",
             errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Export", name: "foo" } }]
         },
+        {
+            code: "export {a as \\u0061} from 'foo';",
+            output: "export {a} from 'foo';",
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Export", name: "a" } }]
+        },
+        {
+            code: "export {\\u0061 as a} from 'foo';",
+            output: "export {\\u0061} from 'foo';",
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Export", name: "a" } }]
+        },
+        {
+            code: "export {\\u0061 as \\u0061} from 'foo';",
+            output: "export {\\u0061} from 'foo';",
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Export", name: "a" } }]
+        },
         {
             code: "export {foo as foo, bar as baz} from 'foo';",
             output: "export {foo, bar as baz} from 'foo';",
@@ -412,6 +492,11 @@ ruleTester.run("no-useless-rename", rule, {
             output: "({/* comment */foo} = {});",
             errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "foo" } }]
         },
+        {
+            code: "({/* comment */foo: foo = 1} = {});",
+            output: "({/* comment */foo = 1} = {});",
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "foo" } }]
+        },
         {
             code: "({foo, /* comment */bar: bar} = {});",
             output: "({foo, /* comment */bar} = {});",
@@ -422,11 +507,21 @@ ruleTester.run("no-useless-rename", rule, {
             output: null,
             errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "foo" } }]
         },
+        {
+            code: "({foo/**/ : foo = 1} = {});",
+            output: null,
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "foo" } }]
+        },
         {
             code: "({foo /**/: foo} = {});",
             output: null,
             errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "foo" } }]
         },
+        {
+            code: "({foo /**/: foo = 1} = {});",
+            output: null,
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "foo" } }]
+        },
         {
             code: "({foo://\nfoo} = {});",
             output: null,
@@ -437,6 +532,36 @@ ruleTester.run("no-useless-rename", rule, {
             output: null,
             errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "foo" } }]
         },
+        {
+            code: "({foo: (/**/foo)} = {});",
+            output: null,
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "foo" } }]
+        },
+        {
+            code: "({foo: (foo/**/)} = {});",
+            output: null,
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "foo" } }]
+        },
+        {
+            code: "({foo: (foo //\n)} = {});",
+            output: null,
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "foo" } }]
+        },
+        {
+            code: "({foo: /**/foo = 1} = {});",
+            output: null,
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "foo" } }]
+        },
+        {
+            code: "({foo: (/**/foo) = 1} = {});",
+            output: null,
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "foo" } }]
+        },
+        {
+            code: "({foo: (foo/**/) = 1} = {});",
+            output: null,
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "foo" } }]
+        },
         {
             code: "({foo: foo/* comment */} = {});",
             output: "({foo/* comment */} = {});",
@@ -447,6 +572,31 @@ ruleTester.run("no-useless-rename", rule, {
             output: "({foo//comment\n,bar} = {});",
             errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "foo" } }]
         },
+        {
+            code: "({foo: foo/* comment */ = 1} = {});",
+            output: "({foo/* comment */ = 1} = {});",
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "foo" } }]
+        },
+        {
+            code: "({foo: foo // comment\n = 1} = {});",
+            output: "({foo // comment\n = 1} = {});",
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "foo" } }]
+        },
+        {
+            code: "({foo: foo = /* comment */ 1} = {});",
+            output: "({foo = /* comment */ 1} = {});",
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "foo" } }]
+        },
+        {
+            code: "({foo: foo = // comment\n 1} = {});",
+            output: "({foo = // comment\n 1} = {});",
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "foo" } }]
+        },
+        {
+            code: "({foo: foo = (1/* comment */)} = {});",
+            output: "({foo = (1/* comment */)} = {});",
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Destructuring assignment", name: "foo" } }]
+        },
         {
             code: "import {/* comment */foo as foo} from 'foo';",
             output: "import {/* comment */foo} from 'foo';",
index ad25f5f483086b60bcb4dae4866842975c1a3937..c591d2365834cdf699468ca66e1bac220a0ee5cc 100644 (file)
@@ -500,9 +500,11 @@ ruleTester.run("prefer-const", rule, {
                 { message: "'b' is never reassigned. Use 'const' instead.", type: "Identifier" }
             ]
         },
+
+        // The inner `let` will be auto-fixed in the second pass
         {
             code: "let someFunc = () => { let a = 1, b = 2; foo(a, b) }",
-            output: "const someFunc = () => { const a = 1, b = 2; foo(a, b) }",
+            output: "const someFunc = () => { let a = 1, b = 2; foo(a, b) }",
             errors: [
                 { message: "'someFunc' is never reassigned. Use 'const' instead.", type: "Identifier" },
                 { message: "'a' is never reassigned. Use 'const' instead.", type: "Identifier" },
@@ -546,6 +548,13 @@ ruleTester.run("prefer-const", rule, {
                 { message: "'bar' is never reassigned. Use 'const' instead.", type: "Identifier" },
                 { message: "'bar' is never reassigned. Use 'const' instead.", type: "Identifier" }
             ]
+        },
+
+        // https://github.com/eslint/eslint/issues/13899
+        {
+            code: "/*eslint no-undef-init:error*/ let foo = undefined;",
+            output: "/*eslint no-undef-init:error*/ const foo = undefined;",
+            errors: 2
         }
     ]
 });
index 52801c3d18313b7d218826412bd75c9b7061f36a..ddb8363c1d9d2c7b837bf20d25dc93439d314093 100644 (file)
@@ -76,7 +76,34 @@ ruleTester.run("radix", rule, {
             code: "parseInt(\"10\");",
             errors: [{
                 messageId: "missingRadix",
-                type: "CallExpression"
+                type: "CallExpression",
+                suggestions: [{ messageId: "addRadixParameter10", output: "parseInt(\"10\", 10);" }]
+            }]
+        },
+        {
+            code: "parseInt(\"10\",);", // Function parameter with trailing comma
+            parserOptions: { ecmaVersion: 2017 },
+            errors: [{
+                messageId: "missingRadix",
+                type: "CallExpression",
+                suggestions: [{ messageId: "addRadixParameter10", output: "parseInt(\"10\", 10,);" }]
+            }]
+        },
+        {
+            code: "parseInt((0, \"10\"));", // Sequence expression (no trailing comma).
+            errors: [{
+                messageId: "missingRadix",
+                type: "CallExpression",
+                suggestions: [{ messageId: "addRadixParameter10", output: "parseInt((0, \"10\"), 10);" }]
+            }]
+        },
+        {
+            code: "parseInt((0, \"10\"),);", // Sequence expression (with trailing comma).
+            parserOptions: { ecmaVersion: 2017 },
+            errors: [{
+                messageId: "missingRadix",
+                type: "CallExpression",
+                suggestions: [{ messageId: "addRadixParameter10", output: "parseInt((0, \"10\"), 10,);" }]
             }]
         },
         {
@@ -154,7 +181,8 @@ ruleTester.run("radix", rule, {
             code: "Number.parseInt(\"10\");",
             errors: [{
                 messageId: "missingRadix",
-                type: "CallExpression"
+                type: "CallExpression",
+                suggestions: [{ messageId: "addRadixParameter10", output: "Number.parseInt(\"10\", 10);" }]
             }]
         },
         {
@@ -191,22 +219,46 @@ ruleTester.run("radix", rule, {
         {
             code: "parseInt?.(\"10\");",
             parserOptions: { ecmaVersion: 2020 },
-            errors: [{ messageId: "missingRadix" }]
+            errors: [
+                {
+                    messageId: "missingRadix",
+                    type: "CallExpression",
+                    suggestions: [{ messageId: "addRadixParameter10", output: "parseInt?.(\"10\", 10);" }]
+                }
+            ]
         },
         {
             code: "Number.parseInt?.(\"10\");",
             parserOptions: { ecmaVersion: 2020 },
-            errors: [{ messageId: "missingRadix" }]
+            errors: [
+                {
+                    messageId: "missingRadix",
+                    type: "CallExpression",
+                    suggestions: [{ messageId: "addRadixParameter10", output: "Number.parseInt?.(\"10\", 10);" }]
+                }
+            ]
         },
         {
             code: "Number?.parseInt(\"10\");",
             parserOptions: { ecmaVersion: 2020 },
-            errors: [{ messageId: "missingRadix" }]
+            errors: [
+                {
+                    messageId: "missingRadix",
+                    type: "CallExpression",
+                    suggestions: [{ messageId: "addRadixParameter10", output: "Number?.parseInt(\"10\", 10);" }]
+                }
+            ]
         },
         {
             code: "(Number?.parseInt)(\"10\");",
             parserOptions: { ecmaVersion: 2020 },
-            errors: [{ messageId: "missingRadix" }]
+            errors: [
+                {
+                    messageId: "missingRadix",
+                    type: "CallExpression",
+                    suggestions: [{ messageId: "addRadixParameter10", output: "(Number?.parseInt)(\"10\", 10);" }]
+                }
+            ]
         }
     ]
 });
index cd51e38ffbdb5726d0cc08d36889af7f9b1a7637..bd3738ac979dda0f23d378174f5a0ec8649cee64 100644 (file)
@@ -53,6 +53,7 @@ ruleTester.run("require-atomic-updates", rule, {
         "let foo; async function x() { foo = condition ? foo : await bar; }",
         "async function x() { let foo; bar(() => { let foo; blah(foo); }); foo += await result; }",
         "let foo; async function x() { foo = foo + 1; await bar; }",
+        "async function x() { foo += await bar; }",
 
 
         /*
@@ -162,6 +163,52 @@ ruleTester.run("require-atomic-updates", rule, {
                 count -= 1
                 return
             }
+        `,
+
+        // https://github.com/eslint/eslint/issues/14208
+        `
+            async function foo(e) {
+            }
+
+            async function run() {
+              const input = [];
+              const props = [];
+
+              for(const entry of input) {
+                const prop = props.find(a => a.id === entry.id) || null;
+                await foo(entry);
+              }
+
+              for(const entry of input) {
+                const prop = props.find(a => a.id === entry.id) || null;
+              }
+
+              for(const entry2 of input) {
+                const prop = props.find(a => a.id === entry2.id) || null;
+              }
+            }
+        `,
+
+        `
+            async function run() {
+              {
+                let entry;
+                await entry;
+              }
+              {
+                let entry;
+                () => entry;
+
+                entry = 1;
+              }
+            }
+        `,
+
+        `
+            async function run() {
+                await a;
+                b = 1;
+            }
         `
     ],
 
@@ -251,7 +298,7 @@ ruleTester.run("require-atomic-updates", rule, {
             errors: [COMPUTED_PROPERTY_ERROR, STATIC_PROPERTY_ERROR]
         },
         {
-            code: "async function x() { foo += await bar; }",
+            code: "let foo = ''; async function x() { foo += await bar; }",
             errors: [VARIABLE_ERROR]
         },
         {
index 2e9edbaa8cfd6f2bb88456f8b9d348196794b342..2e6f423e9199d1b61ad0989d8a5f5cafd5c05920 100644 (file)
@@ -161,15 +161,69 @@ ruleTester.run("space-infix-ops", rule, {
         },
         {
             code: "a?b:c",
-            output: "a ? b:c",
-            errors: [{
-                messageId: "missingSpace",
-                data: { operator: "?" },
-                type: "ConditionalExpression",
-                line: 1,
-                column: 2,
-                endColumn: 3
-            }]
+            output: "a ? b : c",
+            errors: [
+                {
+                    messageId: "missingSpace",
+                    data: { operator: "?" },
+                    type: "ConditionalExpression",
+                    line: 1,
+                    column: 2,
+                    endColumn: 3
+                },
+                {
+                    messageId: "missingSpace",
+                    data: { operator: ":" },
+                    type: "ConditionalExpression",
+                    line: 1,
+                    column: 4,
+                    endColumn: 5
+                }
+            ]
+        },
+        {
+            code: "a? b :c",
+            output: "a ? b : c",
+            errors: [
+                {
+                    messageId: "missingSpace",
+                    data: { operator: "?" },
+                    type: "ConditionalExpression",
+                    line: 1,
+                    column: 2,
+                    endColumn: 3
+                },
+                {
+                    messageId: "missingSpace",
+                    data: { operator: ":" },
+                    type: "ConditionalExpression",
+                    line: 1,
+                    column: 6,
+                    endColumn: 7
+                }
+            ]
+        },
+        {
+            code: "a ?b: c",
+            output: "a ? b : c",
+            errors: [
+                {
+                    messageId: "missingSpace",
+                    data: { operator: "?" },
+                    type: "ConditionalExpression",
+                    line: 1,
+                    column: 3,
+                    endColumn: 4
+                },
+                {
+                    messageId: "missingSpace",
+                    data: { operator: ":" },
+                    type: "ConditionalExpression",
+                    line: 1,
+                    column: 5,
+                    endColumn: 6
+                }
+            ]
         },
         {
             code: "a?b : c",
index 76ca7c7ca2871f92a4a8480a7009f3ef3c604ae9..efe57bb0baa502a04e332e53f32fcee08b2fc3c7 100644 (file)
@@ -12,6 +12,7 @@
 const assert = require("chai").assert;
 const sinon = require("sinon");
 const spawn = require("cross-spawn");
+const os = require("os");
 const { unIndent } = require("../../_utils");
 const RuntimeInfo = require("../../../lib/shared/runtime-info");
 const log = require("../../../lib/shared/logging");
@@ -55,8 +56,12 @@ describe("RuntimeInfo", () => {
         let logErrorStub;
         let originalProcessArgv;
         let spawnSyncStubArgs;
+        const originalOsPlatform = os.platform;
+        const originalOsRelease = os.release;
 
         beforeEach(() => {
+            os.platform = () => "darwin";
+            os.release = () => "20.3.0";
             spawnSyncStub = sinon.stub(spawn, "sync");
             logErrorStub = sinon.stub(log, "error");
             originalProcessArgv = process.argv;
@@ -94,6 +99,8 @@ describe("RuntimeInfo", () => {
             spawnSyncStub.restore();
             logErrorStub.restore();
             process.argv = originalProcessArgv;
+            os.platform = originalOsPlatform;
+            os.release = originalOsRelease;
         });
 
 
@@ -109,6 +116,7 @@ describe("RuntimeInfo", () => {
                     npm version: v6.11.3
                     Local ESLint version: v6.3.0 (Currently used)
                     Global ESLint version: v5.16.0
+                    Operating System: darwin 20.3.0
                 `
             );
         });
@@ -126,6 +134,7 @@ describe("RuntimeInfo", () => {
                     npm version: v6.11.3
                     Local ESLint version: v6.3.0
                     Global ESLint version: v5.16.0 (Currently used)
+                    Operating System: darwin 20.3.0
                 `
             );
         });
@@ -150,6 +159,7 @@ describe("RuntimeInfo", () => {
                     npm version: v6.11.3
                     Local ESLint version: Not found
                     Global ESLint version: v5.16.0 (Currently used)
+                    Operating System: darwin 20.3.0
                 `
             );
         });
@@ -167,6 +177,7 @@ describe("RuntimeInfo", () => {
                     npm version: v6.11.3
                     Local ESLint version: v6.3.0 (Currently used)
                     Global ESLint version: Not found
+                    Operating System: darwin 20.3.0
                 `
             );
         });
diff --git a/eslint/tests/lib/shared/string-utils.js b/eslint/tests/lib/shared/string-utils.js
new file mode 100644 (file)
index 0000000..bc48afa
--- /dev/null
@@ -0,0 +1,41 @@
+/**
+ * @fileoverview Tests for string utils.
+ * @author Stephen Wade
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const assert = require("chai").assert;
+
+const { upperCaseFirst } = require("../../../lib/shared/string-utils");
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+describe("upperCaseFirst", () => {
+    it("uppercases the first letter of a string", () => {
+        assert(upperCaseFirst("e") === "E");
+        assert(upperCaseFirst("alphabet") === "Alphabet");
+        assert(upperCaseFirst("one two three") === "One two three");
+    });
+
+    it("only changes the case of the first letter", () => {
+        assert(upperCaseFirst("alphaBet") === "AlphaBet");
+        assert(upperCaseFirst("one TWO three") === "One TWO three");
+    });
+
+    it("does not change the case if the first letter is already uppercase", () => {
+        assert(upperCaseFirst("E") === "E");
+        assert(upperCaseFirst("Alphabet") === "Alphabet");
+        assert(upperCaseFirst("One Two Three") === "One Two Three");
+    });
+
+    it("properly handles an empty string", () => {
+        assert(upperCaseFirst("") === "");
+    });
+});
diff --git a/eslint/tests/tools/loose-parser.js b/eslint/tests/tools/loose-parser.js
deleted file mode 100644 (file)
index 98e97cc..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * @fileoverview Define a custom parser to ignore recoverable syntax errors.
- * @author Toru Nagashima <https://github.com/mysticatea>
- *
- * no-redeclare rule uses this parser to check redeclarations.
- */
-"use strict";
-
-const acorn = require("acorn");
-const espree = require("espree/lib/espree");
-
-/**
- * Define the parser which ignores recoverable errors.
- * @returns {(parser:acorn.Parser) => acorn.Parser} The function that defines loose parser.
- */
-function loose() {
-    return Parser => class LooseParser extends Parser {
-        raiseRecoverable() { // eslint-disable-line class-methods-use-this
-            // ignore
-        }
-    };
-}
-
-const LooseEspree = acorn.Parser.extend(espree(), loose());
-
-module.exports = {
-    parse(code, options) {
-        return new LooseEspree(options, code).parse();
-    }
-};
index 2b56290b346380dd18b116874d4e99ef2ef2493f..a22c99bf86134ddc06629881ea8d1eeda1029088 100644 (file)
@@ -1,5 +1,7 @@
 "use strict";
+const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
 
+/** @type {import("webpack").Configuration} */
 module.exports = {
     mode: "none",
     entry: {
@@ -37,11 +39,13 @@ module.exports = {
                             targets: ">0.5%, not chrome 49, not ie 11, not safari 5.1"
                         }]
                     ]
-                },
-                exclude: /node_modules[\\/]lodash/u
+                }
             }
         ]
     },
+    plugins: [
+        new NodePolyfillPlugin()
+    ],
     resolve: {
         mainFields: ["browser", "main", "module"]
     },