]> git.proxmox.com Git - pve-eslint.git/commitdiff
import 7.12.1 upstream release
authorThomas Lamprecht <t.lamprecht@proxmox.com>
Thu, 22 Oct 2020 11:01:02 +0000 (13:01 +0200)
committerThomas Lamprecht <t.lamprecht@proxmox.com>
Thu, 29 Oct 2020 10:42:45 +0000 (11:42 +0100)
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
361 files changed:
Makefile
eslint/.eslintrc.js
eslint/.github/CODEOWNERS.md [new file with mode: 0644]
eslint/.github/ISSUE_TEMPLATE.md
eslint/.github/ISSUE_TEMPLATE/BUG_REPORT.md
eslint/CHANGELOG.md
eslint/Makefile.js
eslint/README.md
eslint/conf/config-schema.js
eslint/conf/environments.js [deleted file]
eslint/docs/developer-guide/contributing/README.md
eslint/docs/developer-guide/development-environment.md
eslint/docs/developer-guide/nodejs-api.md
eslint/docs/developer-guide/source-code.md
eslint/docs/developer-guide/working-with-custom-formatters.md
eslint/docs/developer-guide/working-with-plugins.md
eslint/docs/developer-guide/working-with-rules.md
eslint/docs/maintainer-guide/npm-2fa.md [deleted file]
eslint/docs/maintainer-guide/releases.md
eslint/docs/rules/camelcase.md
eslint/docs/rules/comma-spacing.md
eslint/docs/rules/id-blacklist.md
eslint/docs/rules/id-denylist.md [new file with mode: 0644]
eslint/docs/rules/id-length.md
eslint/docs/rules/lines-around-comment.md
eslint/docs/rules/lines-between-class-members.md
eslint/docs/rules/max-lines.md
eslint/docs/rules/multiline-ternary.md
eslint/docs/rules/no-await-in-loop.md
eslint/docs/rules/no-func-assign.md
eslint/docs/rules/no-inline-comments.md
eslint/docs/rules/no-loss-of-precision.md
eslint/docs/rules/no-magic-numbers.md
eslint/docs/rules/no-multiple-empty-lines.md
eslint/docs/rules/no-promise-executor-return.md [new file with mode: 0644]
eslint/docs/rules/no-return-await.md
eslint/docs/rules/no-script-url.md
eslint/docs/rules/no-underscore-dangle.md
eslint/docs/rules/no-unmodified-loop-condition.md
eslint/docs/rules/no-unreachable-loop.md [new file with mode: 0644]
eslint/docs/rules/object-curly-newline.md
eslint/docs/rules/operator-assignment.md
eslint/docs/rules/operator-linebreak.md
eslint/docs/rules/padding-line-between-statements.md
eslint/docs/rules/prefer-destructuring.md
eslint/docs/rules/prefer-object-spread.md
eslint/docs/rules/prefer-regex-literals.md
eslint/docs/rules/sort-imports.md
eslint/docs/rules/space-unary-ops.md
eslint/docs/rules/valid-typeof.md
eslint/docs/user-guide/configuring.md
eslint/docs/user-guide/getting-started.md
eslint/docs/user-guide/migrating-to-4.0.0.md
eslint/docs/user-guide/migrating-to-7.0.0.md
eslint/karma.conf.js
eslint/lib/cli-engine/cascading-config-array-factory.js [deleted file]
eslint/lib/cli-engine/cli-engine.js
eslint/lib/cli-engine/config-array-factory.js [deleted file]
eslint/lib/cli-engine/config-array/config-array.js [deleted file]
eslint/lib/cli-engine/config-array/config-dependency.js [deleted file]
eslint/lib/cli-engine/config-array/extracted-config.js [deleted file]
eslint/lib/cli-engine/config-array/ignore-pattern.js [deleted file]
eslint/lib/cli-engine/config-array/index.js [deleted file]
eslint/lib/cli-engine/config-array/override-tester.js [deleted file]
eslint/lib/cli-engine/file-enumerator.js
eslint/lib/cli-engine/formatters/checkstyle.js
eslint/lib/eslint/eslint.js
eslint/lib/init/autoconfig.js
eslint/lib/init/config-initializer.js
eslint/lib/linter/code-path-analysis/code-path-analyzer.js
eslint/lib/linter/code-path-analysis/code-path-segment.js
eslint/lib/linter/code-path-analysis/code-path-state.js
eslint/lib/linter/code-path-analysis/debug-helpers.js
eslint/lib/linter/config-comment-parser.js
eslint/lib/linter/linter.js
eslint/lib/linter/report-translator.js
eslint/lib/rule-tester/rule-tester.js
eslint/lib/rules/accessor-pairs.js
eslint/lib/rules/array-callback-return.js
eslint/lib/rules/arrow-body-style.js
eslint/lib/rules/arrow-parens.js
eslint/lib/rules/camelcase.js
eslint/lib/rules/consistent-return.js
eslint/lib/rules/constructor-super.js
eslint/lib/rules/curly.js
eslint/lib/rules/dot-location.js
eslint/lib/rules/dot-notation.js
eslint/lib/rules/func-call-spacing.js
eslint/lib/rules/func-name-matching.js
eslint/lib/rules/function-paren-newline.js
eslint/lib/rules/global-require.js
eslint/lib/rules/id-blacklist.js
eslint/lib/rules/id-denylist.js [new file with mode: 0644]
eslint/lib/rules/id-length.js
eslint/lib/rules/id-match.js
eslint/lib/rules/indent.js
eslint/lib/rules/index.js
eslint/lib/rules/key-spacing.js
eslint/lib/rules/keyword-spacing.js
eslint/lib/rules/max-len.js
eslint/lib/rules/max-lines.js
eslint/lib/rules/new-cap.js
eslint/lib/rules/newline-per-chained-call.js
eslint/lib/rules/no-alert.js
eslint/lib/rules/no-constant-condition.js
eslint/lib/rules/no-duplicate-case.js
eslint/lib/rules/no-eval.js
eslint/lib/rules/no-extend-native.js
eslint/lib/rules/no-extra-bind.js
eslint/lib/rules/no-extra-boolean-cast.js
eslint/lib/rules/no-extra-parens.js
eslint/lib/rules/no-implicit-coercion.js
eslint/lib/rules/no-implied-eval.js
eslint/lib/rules/no-import-assign.js
eslint/lib/rules/no-inline-comments.js
eslint/lib/rules/no-irregular-whitespace.js
eslint/lib/rules/no-loss-of-precision.js
eslint/lib/rules/no-magic-numbers.js
eslint/lib/rules/no-obj-calls.js
eslint/lib/rules/no-promise-executor-return.js [new file with mode: 0644]
eslint/lib/rules/no-prototype-builtins.js
eslint/lib/rules/no-script-url.js
eslint/lib/rules/no-self-assign.js
eslint/lib/rules/no-setter-return.js
eslint/lib/rules/no-underscore-dangle.js
eslint/lib/rules/no-unexpected-multiline.js
eslint/lib/rules/no-unneeded-ternary.js
eslint/lib/rules/no-unreachable-loop.js [new file with mode: 0644]
eslint/lib/rules/no-unused-expressions.js
eslint/lib/rules/no-unused-vars.js
eslint/lib/rules/no-useless-call.js
eslint/lib/rules/no-warning-comments.js
eslint/lib/rules/no-whitespace-before-property.js
eslint/lib/rules/object-curly-newline.js
eslint/lib/rules/object-property-newline.js
eslint/lib/rules/operator-assignment.js
eslint/lib/rules/operator-linebreak.js
eslint/lib/rules/padded-blocks.js
eslint/lib/rules/padding-line-between-statements.js
eslint/lib/rules/prefer-arrow-callback.js
eslint/lib/rules/prefer-destructuring.js
eslint/lib/rules/prefer-exponentiation-operator.js
eslint/lib/rules/prefer-numeric-literals.js
eslint/lib/rules/prefer-promise-reject-errors.js
eslint/lib/rules/prefer-regex-literals.js
eslint/lib/rules/prefer-spread.js
eslint/lib/rules/prefer-template.js
eslint/lib/rules/quotes.js
eslint/lib/rules/radix.js
eslint/lib/rules/semi-spacing.js
eslint/lib/rules/sort-imports.js
eslint/lib/rules/space-before-blocks.js
eslint/lib/rules/use-isnan.js
eslint/lib/rules/utils/ast-utils.js
eslint/lib/rules/wrap-iife.js
eslint/lib/rules/yoda.js
eslint/lib/shared/config-ops.js [deleted file]
eslint/lib/shared/config-validator.js
eslint/lib/shared/naming.js [deleted file]
eslint/lib/shared/relative-module-resolver.js
eslint/lib/shared/types.js
eslint/messages/extend-config-missing.txt
eslint/messages/no-config-found.txt
eslint/messages/plugin-conflict.txt
eslint/messages/plugin-invalid.txt [new file with mode: 0644]
eslint/messages/plugin-missing.txt
eslint/messages/whitespace-found.txt
eslint/package.json
eslint/templates/bug-report.md
eslint/tests/_utils/in-memory-fs.js
eslint/tests/_utils/index.js
eslint/tests/bin/eslint.js
eslint/tests/fixtures/code-path-analysis/assignment--do-while-and.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--do-while-or.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--do-while-qq.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--for-and-1.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--for-and-2.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--for-or-1.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--for-or-2.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--for-qq-1.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--for-qq-2.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--if-and-1.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--if-and-2.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--if-and-3.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--if-or-1.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--if-or-2.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--if-or-3.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--if-qq-1.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--if-qq-2.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--logical-and-1.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--logical-and-2.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--logical-and-3.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--logical-or-1.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--logical-or-2.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--logical-or-3.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--logical-qq-1.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--logical-qq-2.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--logical-qq-3.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--multi-1.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--multi-10.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--multi-2.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--multi-3.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--multi-4.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--multi-5.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--multi-6.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--multi-7.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--multi-8.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--multi-9.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--nested-and-1.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--nested-and-2.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--nested-and-3.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--nested-or-1.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--nested-or-2.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--nested-or-3.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--nested-qq-1.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--nested-qq-2.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--nested-qq-3.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--simple-and-1.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--simple-and-2.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--simple-and-3.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--simple-bitand.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--simple-eq.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--simple-or-1.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--simple-or-2.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--simple-or-3.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--simple-plus.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--simple-qq-1.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--simple-qq-2.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--simple-qq-3.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--while-and.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--while-or.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/assignment--while-qq.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/logical--if-qdot-1.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/optional-chaining--complex-1.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/optional-chaining--complex-2.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/optional-chaining--complex-3.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/optional-chaining--simple-1.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/optional-chaining--simple-2.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/optional-chaining--simple-3.js [new file with mode: 0644]
eslint/tests/fixtures/code-path-analysis/optional-chaining--simple-4.js [new file with mode: 0644]
eslint/tests/fixtures/parsers/arrow-parens/generics-extends-complex.js [new file with mode: 0644]
eslint/tests/fixtures/parsers/arrow-parens/generics-extends.js [new file with mode: 0644]
eslint/tests/fixtures/parsers/arrow-parens/generics-simple-async.js [new file with mode: 0644]
eslint/tests/fixtures/parsers/arrow-parens/generics-simple-no-params.js [new file with mode: 0644]
eslint/tests/fixtures/parsers/arrow-parens/generics-simple.js [new file with mode: 0644]
eslint/tests/fixtures/parsers/space-before-blocks/return-type-keyword-1.js [new file with mode: 0644]
eslint/tests/fixtures/parsers/space-before-blocks/return-type-keyword-2.js [new file with mode: 0644]
eslint/tests/fixtures/testers/rule-tester/no-var.js
eslint/tests/lib/cli-engine/cascading-config-array-factory.js [deleted file]
eslint/tests/lib/cli-engine/cli-engine.js
eslint/tests/lib/cli-engine/config-array-factory.js [deleted file]
eslint/tests/lib/cli-engine/config-array/config-array.js [deleted file]
eslint/tests/lib/cli-engine/config-array/config-dependency.js [deleted file]
eslint/tests/lib/cli-engine/config-array/extracted-config.js [deleted file]
eslint/tests/lib/cli-engine/config-array/ignore-pattern.js [deleted file]
eslint/tests/lib/cli-engine/config-array/override-tester.js [deleted file]
eslint/tests/lib/cli-engine/file-enumerator.js
eslint/tests/lib/cli-engine/formatters/checkstyle.js
eslint/tests/lib/cli.js
eslint/tests/lib/eslint/eslint.js
eslint/tests/lib/init/config-file.js
eslint/tests/lib/init/config-initializer.js
eslint/tests/lib/linter/code-path-analysis/code-path-analyzer.js
eslint/tests/lib/linter/report-translator.js
eslint/tests/lib/rule-tester/rule-tester.js
eslint/tests/lib/rules/accessor-pairs.js
eslint/tests/lib/rules/array-callback-return.js
eslint/tests/lib/rules/arrow-body-style.js
eslint/tests/lib/rules/arrow-parens.js
eslint/tests/lib/rules/camelcase.js
eslint/tests/lib/rules/computed-property-spacing.js
eslint/tests/lib/rules/constructor-super.js
eslint/tests/lib/rules/curly.js
eslint/tests/lib/rules/dot-location.js
eslint/tests/lib/rules/dot-notation.js
eslint/tests/lib/rules/func-call-spacing.js
eslint/tests/lib/rules/func-name-matching.js
eslint/tests/lib/rules/function-paren-newline.js
eslint/tests/lib/rules/getter-return.js
eslint/tests/lib/rules/global-require.js
eslint/tests/lib/rules/id-blacklist.js
eslint/tests/lib/rules/id-denylist.js [new file with mode: 0644]
eslint/tests/lib/rules/id-length.js
eslint/tests/lib/rules/indent.js
eslint/tests/lib/rules/key-spacing.js
eslint/tests/lib/rules/keyword-spacing.js
eslint/tests/lib/rules/max-len.js
eslint/tests/lib/rules/max-lines.js
eslint/tests/lib/rules/new-cap.js
eslint/tests/lib/rules/newline-per-chained-call.js
eslint/tests/lib/rules/no-alert.js
eslint/tests/lib/rules/no-bitwise.js
eslint/tests/lib/rules/no-constant-condition.js
eslint/tests/lib/rules/no-dupe-keys.js
eslint/tests/lib/rules/no-duplicate-case.js
eslint/tests/lib/rules/no-eval.js
eslint/tests/lib/rules/no-extend-native.js
eslint/tests/lib/rules/no-extra-bind.js
eslint/tests/lib/rules/no-extra-boolean-cast.js
eslint/tests/lib/rules/no-extra-parens.js
eslint/tests/lib/rules/no-func-assign.js
eslint/tests/lib/rules/no-implicit-coercion.js
eslint/tests/lib/rules/no-implied-eval.js
eslint/tests/lib/rules/no-import-assign.js
eslint/tests/lib/rules/no-inline-comments.js
eslint/tests/lib/rules/no-invalid-this.js
eslint/tests/lib/rules/no-irregular-whitespace.js
eslint/tests/lib/rules/no-loss-of-precision.js
eslint/tests/lib/rules/no-magic-numbers.js
eslint/tests/lib/rules/no-obj-calls.js
eslint/tests/lib/rules/no-param-reassign.js
eslint/tests/lib/rules/no-promise-executor-return.js [new file with mode: 0644]
eslint/tests/lib/rules/no-prototype-builtins.js
eslint/tests/lib/rules/no-restricted-syntax.js
eslint/tests/lib/rules/no-script-url.js
eslint/tests/lib/rules/no-self-assign.js
eslint/tests/lib/rules/no-setter-return.js
eslint/tests/lib/rules/no-this-before-super.js
eslint/tests/lib/rules/no-throw-literal.js
eslint/tests/lib/rules/no-undef.js
eslint/tests/lib/rules/no-underscore-dangle.js
eslint/tests/lib/rules/no-unexpected-multiline.js
eslint/tests/lib/rules/no-unneeded-ternary.js
eslint/tests/lib/rules/no-unreachable-loop.js [new file with mode: 0644]
eslint/tests/lib/rules/no-unused-expressions.js
eslint/tests/lib/rules/no-useless-call.js
eslint/tests/lib/rules/no-warning-comments.js
eslint/tests/lib/rules/no-whitespace-before-property.js
eslint/tests/lib/rules/object-curly-newline.js
eslint/tests/lib/rules/object-property-newline.js
eslint/tests/lib/rules/operator-assignment.js
eslint/tests/lib/rules/operator-linebreak.js
eslint/tests/lib/rules/padding-line-between-statements.js
eslint/tests/lib/rules/prefer-arrow-callback.js
eslint/tests/lib/rules/prefer-destructuring.js
eslint/tests/lib/rules/prefer-exponentiation-operator.js
eslint/tests/lib/rules/prefer-numeric-literals.js
eslint/tests/lib/rules/prefer-promise-reject-errors.js
eslint/tests/lib/rules/prefer-regex-literals.js
eslint/tests/lib/rules/prefer-spread.js
eslint/tests/lib/rules/prefer-template.js
eslint/tests/lib/rules/quote-props.js
eslint/tests/lib/rules/quotes.js
eslint/tests/lib/rules/radix.js
eslint/tests/lib/rules/semi-spacing.js
eslint/tests/lib/rules/sort-imports.js
eslint/tests/lib/rules/space-before-blocks.js
eslint/tests/lib/rules/space-infix-ops.js
eslint/tests/lib/rules/use-isnan.js
eslint/tests/lib/rules/utils/ast-utils.js
eslint/tests/lib/rules/wrap-iife.js
eslint/tests/lib/rules/yoda.js
eslint/tests/lib/shared/config-ops.js [deleted file]
eslint/tests/lib/shared/config-validator.js
eslint/tests/lib/shared/naming.js [deleted file]
eslint/tests/lib/source-code/source-code.js
eslint/tests/tools/internal-rules/consistent-docs-url.js
eslint/tools/code-sample-minimizer.js
eslint/tools/internal-rules/consistent-docs-url.js
eslint/tools/rule-types.json
eslint/webpack.config.js

index 44a29a1479d83aa1679480095192e127c7503b2b..16d5de3e55ef71c5413653e363ba3f611f0947fc 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,7 @@ DSC=${PACKAGE}_${DEB_VERSION_UPSTREAM_REVISION}.dsc
 
 SRCDIR=src
 UPSTREAM=eslint
-UPSTREAMTAG=v7.2.0
+UPSTREAMTAG=v7.12.1
 BUILDSRC=${UPSTREAM}-${UPSTREAMTAG}
 
 all: ${DEB}
index a53fedba15b9df2edf6a2add43aa4d75dc242262..1457d9b553c05f89e8414282632af23019038568 100644 (file)
@@ -95,12 +95,8 @@ module.exports = {
             files: ["lib/rules/*", "tools/internal-rules/*"],
             excludedFiles: ["index.js"],
             rules: {
-                "internal-rules/no-invalid-meta": "error"
-
-                /*
-                 * TODO: enable it when all the rules using meta.messages
-                 * "internal-rules/consistent-meta-messages": "error"
-                 */
+                "internal-rules/no-invalid-meta": "error",
+                "internal-rules/consistent-meta-messages": "error"
             }
         },
         {
diff --git a/eslint/.github/CODEOWNERS.md b/eslint/.github/CODEOWNERS.md
new file mode 100644 (file)
index 0000000..b032d30
--- /dev/null
@@ -0,0 +1,17 @@
+# Config-related files
+
+lib/conf/config-schema.js @nzakas
+lib/cli-engine/config-array/* @nzakas
+lib/cli-engine/config-array-factory.js @nzakas
+lib/cli-engine/cascading-config-array-factory.js @nzakas
+lib/shared/config-* @nzakas
+lib/shared/naming.js @nzakas
+lib/shared/relative-module-resolver.js @nzakas
+
+tests/lib/conf/config-schema.js @nzakas
+tests/lib/cli-engine/config-array/* @nzakas
+tests/lib/cli-engine/config-array-factory.js @nzakas
+tests/lib/cli-engine/cascading-config-array-factory.js @nzakas
+tests/lib/shared/config-* @nzakas
+tests/lib/shared/naming.js @nzakas
+tests/lib/shared/relative-module-resolver.js @nzakas
index 44fe78004d025e78fbd73a5b80182c7d5ef08271..3af794af7fdf3c3cd8700c952462305524a30a6b 100644 (file)
@@ -22,7 +22,7 @@
 * **Node Version:**
 * **npm Version:**
 
-**What parser (default, Babel-ESLint, etc.) are you using?**
+**What parser (default, `@babel/eslint-parser`, `@typescript-eslint/parser`, etc.) are you using?**
 
 **Please show your full configuration:**
 
index 175871510d784721779bf3b44c4c2a5df693a520..1b1699aae0214b92cacb8f0a1cb2b98a04df1051 100644 (file)
@@ -32,7 +32,7 @@ assignees: ''
 * **Node Version:**
 * **npm Version:**
 
-**What parser (default, Babel-ESLint, etc.) are you using?**
+**What parser (default, `@babel/eslint-parser`, `@typescript-eslint/parser`, etc.) are you using?**
 
 **Please show your full configuration:**
 
index 52ff78da098417b696ebba9ef3d571e5fb0320e2..75b641ea6183643c875282ac90e626d1b1e070e3 100644 (file)
@@ -1,3 +1,226 @@
+v7.12.1 - October 26, 2020
+
+* [`08f33e8`](https://github.com/eslint/eslint/commit/08f33e8b9a353c3183be6f937785db7a30fb90eb) Upgrade: @eslint/eslintrc to fix rule schema validation (fixes #13793) (#13794) (Brandon Mills)
+* [`aeef485`](https://github.com/eslint/eslint/commit/aeef485dc790571b1a82ac09904329e0226b66a9) Fix: Pass internal config paths in FileEnumerator default (fixes #13789) (#13792) (Brandon Mills)
+* [`631ae8b`](https://github.com/eslint/eslint/commit/631ae8b50e5f7975f10860e9e763b70b4f25182e) Sponsors: Sync README with website (ESLint Jenkins)
+
+v7.12.0 - October 23, 2020
+
+* [`cbf3585`](https://github.com/eslint/eslint/commit/cbf3585f1d6c60414c07380367a8b4505ee3538d) Update: skip keyword check for fns in space-before-blocks (fixes #13553) (#13712) (Milos Djermanovic)
+* [`256f656`](https://github.com/eslint/eslint/commit/256f656455b47bcf9ed3fc30fbf72532678f97da) Fix: autofix shouldn't produce template literals with `\8` or `\9` (#13737) (Milos Djermanovic)
+* [`b165aa5`](https://github.com/eslint/eslint/commit/b165aa5f4d4d19328f13ab80e5f058cbce94c3a6) Fix: yoda rule autofix produces syntax errors with adjacent tokens (#13760) (Milos Djermanovic)
+* [`3175316`](https://github.com/eslint/eslint/commit/3175316db26aebef4b19e269aca90c8ce3955363) Fix: prefer-destructuring invalid autofix with comma operator (#13761) (Milos Djermanovic)
+* [`1a9f171`](https://github.com/eslint/eslint/commit/1a9f17151a4e93eb17c8a2bf4f0a5320cce616de) Chore: Remove more ESLintRC-related files (refs #13481) (#13762) (Nicholas C. Zakas)
+* [`bfddced`](https://github.com/eslint/eslint/commit/bfddcedace5587d662c840c2edf33062b54a178e) Update: remove suggestion if it didn't provide a fix (fixes #13723) (#13772) (Milos Djermanovic)
+* [`5183b14`](https://github.com/eslint/eslint/commit/5183b14a2420b42b4089fb134a61ae57142f31fd) Update: check template literal in no-script-url (#13775) (YeonJuan)
+* [`bfe97d2`](https://github.com/eslint/eslint/commit/bfe97d2332e711ca76b1fd2e7f8548b0cc84cb1c) Sponsors: Sync README with website (ESLint Jenkins)
+* [`6c51ade`](https://github.com/eslint/eslint/commit/6c51adeb86f1de292cd02d2ee19f7b56182e358b) Sponsors: Sync README with website (ESLint Jenkins)
+* [`603de04`](https://github.com/eslint/eslint/commit/603de04cab5e700df12999af2918decd4da9d11b) Update: treat all literals like boolean literal in no-constant-condition (#13245) (Zen)
+* [`289aa6f`](https://github.com/eslint/eslint/commit/289aa6fcef3874ba5f86455f9302dc4209ea83e5) Sponsors: Sync README with website (ESLint Jenkins)
+* [`9a1f669`](https://github.com/eslint/eslint/commit/9a1f6694e59eb3e584d4c5a98b98675c895a9783) Sponsors: Sync README with website (ESLint Jenkins)
+* [`637f818`](https://github.com/eslint/eslint/commit/637f8187404ded600fb3d4013b3cd495d5ae675b) Docs: add more examples for no-func-assign (fixes #13705) (#13777) (Nitin Kumar)
+* [`17cc0dd`](https://github.com/eslint/eslint/commit/17cc0dd9b5d2d500359c36881cd3e5637443c133) Chore: add test case for no-func-assign (refs #13705) (#13783) (Nitin Kumar)
+* [`dee0f77`](https://github.com/eslint/eslint/commit/dee0f7764a1d5a323c89b22c4db94acee2b3c718) Docs: add TOC to user-guide/configuring.md (#13727) (metasean)
+* [`0510621`](https://github.com/eslint/eslint/commit/05106212985cb1ffa1e6fa996a57f6fd2fc3c970) Update: Fix && vs || short-circuiting false negatives (fixes #13634) (#13769) (Brandon Mills)
+* [`8b6ed69`](https://github.com/eslint/eslint/commit/8b6ed691c48189b7d096339441a78cb5874d4137) Sponsors: Sync README with website (ESLint Jenkins)
+* [`1457509`](https://github.com/eslint/eslint/commit/145750991b04fd4cfb3fff3c5d4211a4428e011c) Docs: fix broken links in Node.js API docs (#13771) (Laura Barluzzi)
+* [`7c813d4`](https://github.com/eslint/eslint/commit/7c813d458f9aedf7a94351d137728a4647542879) Docs: Fix typo in v7 migration page (#13778) (Yusuke Sasaki)
+* [`b025795`](https://github.com/eslint/eslint/commit/b0257953be704d0bb387fc15afd7859fd6f19ba5) Docs: Fix the format option name in the document (#13770) (Hideki Igarashi)
+* [`84fd591`](https://github.com/eslint/eslint/commit/84fd591c234accc41bb5af555f178825012fd35d) Chore: Increase Mocha timeout for copying fixtures (#13768) (Brandon Mills)
+* [`1faeb84`](https://github.com/eslint/eslint/commit/1faeb84e663d88c5d85a3cb3f15cd224cc552c2d) Docs: clarify that space-unary-ops doesn't apply when space is required (#13767) (Taylor Morgan)
+* [`67c0605`](https://github.com/eslint/eslint/commit/67c06059dd1ddcee6f369c650ce71220da1510c3) Update: check computed keys in no-prototype-builtins (fixes #13088) (#13755) (Milos Djermanovic)
+* [`b5e011c`](https://github.com/eslint/eslint/commit/b5e011c865e95d700d29cb9a4ba71c671d99e423) Sponsors: Sync README with website (ESLint Jenkins)
+
+v7.11.0 - October 9, 2020
+
+* [`23e966f`](https://github.com/eslint/eslint/commit/23e966f6cf2a6c6b699dff5d6950ece3cc396498) Chore: Refactor CLIEngine tests (refs #13481) (#13709) (Nicholas C. Zakas)
+* [`fa9429a`](https://github.com/eslint/eslint/commit/fa9429aac0ffed505f3f02e8fc75f646c69f5c61) Fix: don't count line after EOF in max-lines (#13735) (Milos Djermanovic)
+* [`d973675`](https://github.com/eslint/eslint/commit/d973675a5c06a2bd4f8ce640c78b67842cfebfd4) Docs: Update anchor links to use existing linkrefs (refs #13715) (#13741) (Brandon Mills)
+* [`2c6d774`](https://github.com/eslint/eslint/commit/2c6d774c89dcd14f386bd9d73d451fa2a892c3ef) Docs: Fix typos (#13730) (Frieder Bluemle)
+* [`cc468c0`](https://github.com/eslint/eslint/commit/cc468c01021385a028de727eefcd442e7f34875c) Upgrade: eslint-visitor-keys@2.0.0 (#13732) (Milos Djermanovic)
+* [`ab0ac6c`](https://github.com/eslint/eslint/commit/ab0ac6c532fb7b7d49779c8913146244d680743b) Docs: Fix anchor links (#13715) (Gary Moore)
+* [`27f0de6`](https://github.com/eslint/eslint/commit/27f0de62e6281c28043be38ef051818c9edc15cd) Fix: account for linebreaks before postfix `++`/`--` in no-extra-parens (#13731) (Milos Djermanovic)
+* [`da78fa1`](https://github.com/eslint/eslint/commit/da78fa11632a2908db4ac494012a16f5d5a88a64) Update: support async arrow fn in function-paren-newline (fixes #13728) (#13729) (Michal Dziekonski)
+* [`fe301b8`](https://github.com/eslint/eslint/commit/fe301b8cc0762d7f4edd59603ca51ed0ec0c2a43) Docs: Add configuration comments in examples (#13738) (YeonJuan)
+* [`504408c`](https://github.com/eslint/eslint/commit/504408cd65e9d8827b2b8bbeb8f589df90eee523) Sponsors: Sync README with website (ESLint Jenkins)
+* [`3900659`](https://github.com/eslint/eslint/commit/390065985b2289ad4412a83598e3e833c382d27e) Sponsors: Sync README with website (ESLint Jenkins)
+* [`c1974b3`](https://github.com/eslint/eslint/commit/c1974b3f7169a8e5fab7007df92d02d8c1a8d5a3) Sponsors: Sync README with website (ESLint Jenkins)
+* [`6f4abe5`](https://github.com/eslint/eslint/commit/6f4abe5d5ade2711cc4c21bc8485af952763c2d3) Sponsors: Sync README with website (ESLint Jenkins)
+
+v7.10.0 - September 26, 2020
+
+* [`6919fbb`](https://github.com/eslint/eslint/commit/6919fbb83f86552b0f49ae749da866e4edc7c46a) Docs: Clarify that ignorePattern should be a string (refs #13029) (#13718) (Brandon Mills)
+* [`07d9bea`](https://github.com/eslint/eslint/commit/07d9bea7c6f953e8f754afffc9752edcee799431) Update: Add ignorePattern to no-inline-comments (#13029) (Edie Lemoine)
+* [`d79bbe9`](https://github.com/eslint/eslint/commit/d79bbe982930b53358d34ad91cc6e5eaac8ddede) Docs: fix typo (#13717) (Alexander Liu)
+* [`9b8490e`](https://github.com/eslint/eslint/commit/9b8490ee6391c986b1314540a92b71d8c1e0efc4) Docs: grammatical error (#13687) (rajdeep)
+* [`cb44e93`](https://github.com/eslint/eslint/commit/cb44e93f4780e925a75a68ce2f7f6d065b5f756c) Fix: prefer-destructuring invalid autofix with computed property access (#13704) (Milos Djermanovic)
+* [`46c73b1`](https://github.com/eslint/eslint/commit/46c73b159a5ceed2f7f26f254fd97e459fb0e81a) Upgrade: eslint-scope@5.1.1 (#13716) (Milos Djermanovic)
+* [`b7b12ba`](https://github.com/eslint/eslint/commit/b7b12ba0bd4e9c66883f11e97de8ed84b600cdaa) Chore: Move comment to make tests more organized (#13707) (Yusuke Tanaka)
+* [`51674a4`](https://github.com/eslint/eslint/commit/51674a4113a1ca877094606bbf4938ab06cc1aad) Docs: Add missing quotes (#13714) (Lucio Paiva)
+* [`7c34a98`](https://github.com/eslint/eslint/commit/7c34a982aaf93a02348f56c9ce887c7dcf51b5bd) Chore: remove mistakenly added file (#13710) (Milos Djermanovic)
+* [`30b76c9`](https://github.com/eslint/eslint/commit/30b76c9a13fae3dff59f7db406d6c66f11152973) Docs: Clarify package.json requirement in Getting Started (refs #13549) (#13696) (Nicholas C. Zakas)
+* [`044560d`](https://github.com/eslint/eslint/commit/044560dcc74db98b28e293da2e2f3b41ecbf5884) Sponsors: Sync README with website (ESLint Jenkins)
+* [`54000d1`](https://github.com/eslint/eslint/commit/54000d13f27d5255851b5ac0606ad027e2b8d331) Sponsors: Sync README with website (ESLint Jenkins)
+
+v7.9.0 - September 12, 2020
+
+* [`3ca2700`](https://github.com/eslint/eslint/commit/3ca27004ece5016ba7aed775f01ad13bc9282296) Fix: Corrected notice for invalid (:) plugin names (#13473) (Josh Goldberg)
+* [`fc5783d`](https://github.com/eslint/eslint/commit/fc5783d2ff9e3b0d7a1f9664928d49270b4a6c01) Docs: Fix leaky anchors in v4 migration page (#13635) (Timo Tijhof)
+* [`f1d07f1`](https://github.com/eslint/eslint/commit/f1d07f112be96c64dfdaa154aa9ac81985b16238) Docs: Provide install commands for Yarn (#13661) (Nikita Baksalyar)
+* [`29d1cdc`](https://github.com/eslint/eslint/commit/29d1cdceedd6c056a39149723cf9ff2fbb260cbf) Fix: prefer-destructuring removes comments (refs #13678) (#13682) (Milos Djermanovic)
+* [`b4da0a7`](https://github.com/eslint/eslint/commit/b4da0a7ca7995435bdfc116fd374eb0649470131) Docs: fix typo in working with plugins docs (#13683) (啸生)
+* [`6f87db7`](https://github.com/eslint/eslint/commit/6f87db7c318225e48ccbbf0bec8b3758ea839b82) Update: fix id-length false negatives on Object.prototype property names (#13670) (Milos Djermanovic)
+* [`361ac4d`](https://github.com/eslint/eslint/commit/361ac4d895c15086fb4351d4dca1405b2fdc4bd5) Fix: NonOctalDecimalIntegerLiteral is decimal integer (fixes #13588) (#13664) (Milos Djermanovic)
+* [`f260716`](https://github.com/eslint/eslint/commit/f260716695064e4b4193337107b60401bd4b3f20) Docs: update outdated link (#13677) (klkhan)
+* [`5138c91`](https://github.com/eslint/eslint/commit/5138c913c256e4266ffb68278783af45bf70af84) Docs: add missing eslint directive comments in no-await-in-loop (#13673) (Milos Djermanovic)
+* [`17b58b5`](https://github.com/eslint/eslint/commit/17b58b528df62bf96813d50c087cafdf83306810) Docs: clarify correct example in no-return-await (fixes #13656) (#13657) (Milos Djermanovic)
+* [`9171f0a`](https://github.com/eslint/eslint/commit/9171f0a99bb4d7c53f109b1c2b215004a7c27713) Chore: fix typo (#13660) (Nitin Kumar)
+* [`6d9f8fb`](https://github.com/eslint/eslint/commit/6d9f8fbb7ed4361b475fb50d04e6d25744d5b1a2) Sponsors: Sync README with website (ESLint Jenkins)
+* [`97b0dd9`](https://github.com/eslint/eslint/commit/97b0dd9a1af1ae4ae3857adcfe6eeac7837101ed) Sponsors: Sync README with website (ESLint Jenkins)
+* [`deab125`](https://github.com/eslint/eslint/commit/deab125fc9220dab43baeb32c6cf78942ad25a83) Sponsors: Sync README with website (ESLint Jenkins)
+* [`bf2e367`](https://github.com/eslint/eslint/commit/bf2e367bf4f6fde9930af9de8b8d8bc3d8b5782f) Sponsors: Sync README with website (ESLint Jenkins)
+* [`8929208`](https://github.com/eslint/eslint/commit/89292084bf91ba5ae5bf966c6c56fa3da139ce57) Sponsors: Sync README with website (ESLint Jenkins)
+
+v7.8.1 - September 1, 2020
+
+* [`f542b5d`](https://github.com/eslint/eslint/commit/f542b5d0679b73326ad249fc44a54c3f848bd3e6) Fix: Update broken @eslint/eslintrc version (fixes #13641) (#13647) (Nicholas C. Zakas)
+* [`c1b5696`](https://github.com/eslint/eslint/commit/c1b56966c2354e12d16e8394443de49fa54f4290) Sponsors: Sync README with website (ESLint Jenkins)
+* [`8ddeda0`](https://github.com/eslint/eslint/commit/8ddeda01afdb1e9656a43853b8e25c9c4582e6ad) Sponsors: Sync README with website (ESLint Jenkins)
+* [`e02e2fe`](https://github.com/eslint/eslint/commit/e02e2fe019a1ed9a34a7b96e4c8961c35093b0ce) Sponsors: Sync README with website (ESLint Jenkins)
+
+v7.8.0 - August 31, 2020
+
+* [`58abd93`](https://github.com/eslint/eslint/commit/58abd9311900a8af5a3c0963daaf64675bdd8383) Update: support logical assignments in code path analysis (refs #13569) (#13612) (Milos Djermanovic)
+* [`db7488e`](https://github.com/eslint/eslint/commit/db7488e6326fd1b7ea04c5062beb1c5f75fc15ed) Update: support logical assignments in core rules (refs #13569) (#13618) (Milos Djermanovic)
+* [`3729219`](https://github.com/eslint/eslint/commit/372921924778f2e525535985e17c97b988546210) Docs: Update Step 1 of Development Environment documentation (klkhan)
+* [`a320324`](https://github.com/eslint/eslint/commit/a32032430a0779a4e3b2d137d4d0682844084b82) Chore: Test formatted integers in no-dupe-keys (refs #13568) (#13626) (Brandon Mills)
+* [`88a9ade`](https://github.com/eslint/eslint/commit/88a9ade7643bb166efbab45cee15f3269496f4be) Update: add es2021 environment (refs #13602) (#13603) (Milos Djermanovic)
+* [`0003dc0`](https://github.com/eslint/eslint/commit/0003dc0f966f2b47555595586f84eb3163cb0179) Update: support numeric separators (refs #13568) (#13581) (Milos Djermanovic)
+* [`96b11a0`](https://github.com/eslint/eslint/commit/96b11a0717bf32b94ec768611574372320fb774b) Update: Add exceptionPatterns to id-length rule (fixes #13094) (#13576) (sodam)
+* [`3439fea`](https://github.com/eslint/eslint/commit/3439fea5c0ed330d01d874b0c9df51dd51ae792c) Update: support numeric-separator in no-loss-of-precision (refs #13568) (#13574) (Anix)
+* [`ed64767`](https://github.com/eslint/eslint/commit/ed64767859d776145d68145419a61f5379b4dd63) Update: add comment to message in no-warning-comments (fixes #12327) (#13522) (Anix)
+* [`e60ec07`](https://github.com/eslint/eslint/commit/e60ec07fad0c1d4c966f28d214c5379da753ff4e) Sponsors: Sync README with website (ESLint Jenkins)
+* [`483bf7f`](https://github.com/eslint/eslint/commit/483bf7f3cc40e0d866798d6ca9ee1c19aa77ddd2) Docs: fix examples in object-curly-newline (#13605) (Soobin Bak)
+* [`1c35d57`](https://github.com/eslint/eslint/commit/1c35d57b0a5f374cc55f1727a7561bcab1962e83) Docs: Remove stale Keybase 2FA instructions (#13622) (Brandon Mills)
+* [`82669fa`](https://github.com/eslint/eslint/commit/82669fa66670a00988db5b1d10fe8f3bf30be84e) Chore: Extract some functionality to eslintrc (refs #13481) (#13613) (Nicholas C. Zakas)
+* [`4111d21`](https://github.com/eslint/eslint/commit/4111d21a046b73892e2c84f92815a21ef4db63e1) Docs: Fix typo and missing article before noun in docs (#13611) (Patrice Sandhu)
+* [`091e52a`](https://github.com/eslint/eslint/commit/091e52ae1ca408f3e668f394c14d214c9ce806e6) Upgrade: espree@7.3.0 (refs #13568) (#13609) (Kai Cataldo)
+* [`05074fb`](https://github.com/eslint/eslint/commit/05074fb2c243e904e8c09d714ad9d084acdd80d2) Sponsors: Sync README with website (ESLint Jenkins)
+* [`bdb65ec`](https://github.com/eslint/eslint/commit/bdb65ec2e672c9815bee356b61d1cd60a1072152) Chore: add 3rd party parsers in BUG_REPORT template (#13606) (YeonJuan)
+* [`f954476`](https://github.com/eslint/eslint/commit/f954476fb6b0664679c73babd5e8a0647572b81f) Chore: add common 3rd party parsers to issue template (#13596) (Kai Cataldo)
+* [`2bee6d2`](https://github.com/eslint/eslint/commit/2bee6d256ae0516c9a9003bb3fdca24ff93253b5) Chore: Mark config-related files (refs #13481) (#13597) (Nicholas C. Zakas)
+* [`66442a9`](https://github.com/eslint/eslint/commit/66442a9faf9872db4a40f56dde28c48f4d02fc7b) Update: Add no-magic-numbers 'ignoreDefaultValues' option (#12611) (Dieter Luypaert)
+* [`b487164`](https://github.com/eslint/eslint/commit/b487164d01dd0bf66fdf2df0e374ce1c3bdb0339) Docs: add exponentiation operators to operator-assignment documentation (#13577) (Milos Djermanovic)
+* [`2f27836`](https://github.com/eslint/eslint/commit/2f27836e989f3dfe236e34054b490febc359bc48) Sponsors: Sync README with website (ESLint Jenkins)
+* [`60eafc1`](https://github.com/eslint/eslint/commit/60eafc15075f38955cb6816bf1f0bcf6e6e6d3a6) Sponsors: Sync README with website (ESLint Jenkins)
+
+v7.7.0 - August 14, 2020
+
+* [`b46f3ee`](https://github.com/eslint/eslint/commit/b46f3ee0dae4add9df99cae940b641ad8de58b9e) Update: allowFunctionParams option in no-underscore-dangle (fixes 12579) (#13545) (Sunghyun Cho)
+* [`26aa245`](https://github.com/eslint/eslint/commit/26aa2452b5f407fabc25dad21182180e4d3be532) Docs: clarify "case" specifier in padding-line-between-statements (#13562) (Milos Djermanovic)
+* [`082891c`](https://github.com/eslint/eslint/commit/082891c042d72953fe86cd3ce9c96e661760793d) Docs: Update semantic versioning policy (#13563) (Nicholas C. Zakas)
+* [`4e0b672`](https://github.com/eslint/eslint/commit/4e0b672eb4bf39f7502a550b08b25a56a196f19f) Fix: revert "Update: disallow multiple options in comma-dangle schema" (#13564) (Kai Cataldo)
+* [`254990e`](https://github.com/eslint/eslint/commit/254990e87914457ca25ea2d7ee012964e56fc9e5) Fix: indent for async arrow functions (fixes #13497) (#13544) (Anix)
+* [`28ca339`](https://github.com/eslint/eslint/commit/28ca339259b07c96c73f2ef28cbf112b96395855) Sponsors: Sync README with website (ESLint Jenkins)
+* [`2e4158d`](https://github.com/eslint/eslint/commit/2e4158d3ec9cfed6400bf70795fd7171e96ff9b3) Sponsors: Sync README with website (ESLint Jenkins)
+* [`488d159`](https://github.com/eslint/eslint/commit/488d1595aef43c4d52cccdb2c97977884f0375a8) Sponsors: Sync README with website (ESLint Jenkins)
+* [`c44306e`](https://github.com/eslint/eslint/commit/c44306e52778309a79232ceab8b55a9aa0f2dfda) Sponsors: Sync README with website (ESLint Jenkins)
+* [`6677180`](https://github.com/eslint/eslint/commit/6677180495e16a02d150d0552e7e5d5f6b77fcc5) Sponsors: Sync README with website (ESLint Jenkins)
+* [`07db7b8`](https://github.com/eslint/eslint/commit/07db7b8080c2f68ee28e7d447db356c33e6fddce) Sponsors: Sync README with website (ESLint Jenkins)
+* [`d4ce4d3`](https://github.com/eslint/eslint/commit/d4ce4d3b8492c3e4654ed1f51f2c48e6c0ad272f) Sponsors: Sync README with website (ESLint Jenkins)
+* [`284e954`](https://github.com/eslint/eslint/commit/284e954f93126c50e0aa9b88f42afb03a47ad967) Sponsors: Sync README with website (ESLint Jenkins)
+* [`ae9b54e`](https://github.com/eslint/eslint/commit/ae9b54e59b01aa9f50ee31f5b6787d86e6b59de6) Sponsors: Sync README with website (ESLint Jenkins)
+* [`9124a15`](https://github.com/eslint/eslint/commit/9124a1599638a1caf4b7e252d1cb66abdc5e51c6) Chore: remove leche (fixes #13287) (#13533) (Mark de Dios)
+* [`5c4c7f5`](https://github.com/eslint/eslint/commit/5c4c7f515c2e8e83f2186a66ddce75d6477abeb0) Sponsors: Sync README with website (ESLint Jenkins)
+* [`48d8ec8`](https://github.com/eslint/eslint/commit/48d8ec8cf320c69aed17c6b6c78f19e7c1e587ca) Sponsors: Sync README with website (ESLint Jenkins)
+
+v7.6.0 - July 31, 2020
+
+* [`ecb2b73`](https://github.com/eslint/eslint/commit/ecb2b7343a0d14fb57d297a16be6c1b176fb3dbf) Update: require `meta` for fixable rules in RuleTester (refs #13349) (#13489) (Milos Djermanovic)
+* [`6fb4edd`](https://github.com/eslint/eslint/commit/6fb4edde3b7a7ae2faf8ac956a7342fbf80865fc) Docs: fix broken links in developer guide (#13518) (Sam Chen)
+* [`318fe10`](https://github.com/eslint/eslint/commit/318fe103dbf2548eee293ff456ef0b829dbe3db3) Fix: Do not output `undefined` as line and column when it's unavailable (#13519) (haya14busa)
+* [`493b5b4`](https://github.com/eslint/eslint/commit/493b5b40cae7a076fdeb19740f8c88fb4ae9c1fb) Sponsors: Sync README with website (ESLint Jenkins)
+* [`f100143`](https://github.com/eslint/eslint/commit/f100143fa5f529aacb2b50e650a00d2697ca4c54) Sponsors: Sync README with website (ESLint Jenkins)
+* [`16b10fe`](https://github.com/eslint/eslint/commit/16b10fe8ba3c78939d5ada4a25caf2f0c9e6a058) Fix: Update the chatroom link to go directly to help channel (#13536) (Nicholas C. Zakas)
+* [`f937eb9`](https://github.com/eslint/eslint/commit/f937eb95407f60d3772bcb956e227aaf99e48777) Sponsors: Sync README with website (ESLint Jenkins)
+* [`e71e298`](https://github.com/eslint/eslint/commit/e71e2980cd2e319afc70d8c859c7ffd59cf4157b) Update: Change no-duplicate-case to comparing tokens (fixes #13485) (#13494) (Yosuke Ota)
+* [`6c4aea4`](https://github.com/eslint/eslint/commit/6c4aea44fd78e1eecea5fe3c37e1921e3b1e98a6) Docs: add ECMAScript 2020 to README (#13510) (Milos Djermanovic)
+
+v7.5.0 - July 18, 2020
+
+* [`6ea3178`](https://github.com/eslint/eslint/commit/6ea3178776eae0e40c3f5498893e8aab0e23686b) Update: optional chaining support (fixes #12642) (#13416) (Toru Nagashima)
+* [`540b1af`](https://github.com/eslint/eslint/commit/540b1af77278ae649b621aa8d4bf8d6de03c3155) Chore: enable consistent-meta-messages internal rule (#13487) (Milos Djermanovic)
+* [`885a145`](https://github.com/eslint/eslint/commit/885a1455691265db88dc0befe9b48a69d69e8b9c) Docs: clarify behavior if `meta.fixable` is omitted (refs #13349) (#13493) (Milos Djermanovic)
+* [`1a01b42`](https://github.com/eslint/eslint/commit/1a01b420eaab0de03dab5cc190a9f2a860c21a84) Docs: Update technology sponsors in README (#13478) (Nicholas C. Zakas)
+* [`6ed9e8e`](https://github.com/eslint/eslint/commit/6ed9e8e4ff038c0259b0e7fe7ab7f4fd4ec55801) Upgrade: lodash@4.17.19 (#13499) (Yohan Siguret)
+* [`45cdf00`](https://github.com/eslint/eslint/commit/45cdf00da6aeff3d584d37b0710fc8d6ad9456d6) Sponsors: Sync README with website (ESLint Jenkins)
+* [`f1cc725`](https://github.com/eslint/eslint/commit/f1cc725ba1b8646dcf06a83716d96ad9bb726172) Docs: fix linebreaks between versions in changelog (#13488) (Milos Djermanovic)
+* [`f4d7b9e`](https://github.com/eslint/eslint/commit/f4d7b9e1a599346b2f21ff9de003b311b51411e6) Update: deprecate id-blacklist rule (#13465) (Dimitri Mitropoulos)
+* [`e14a645`](https://github.com/eslint/eslint/commit/e14a645aa495558081490f990ba221e21aa6b27c) Chore: use espree.latestEcmaVersion in fuzzer (#13484) (Milos Djermanovic)
+* [`61097fe`](https://github.com/eslint/eslint/commit/61097fe5cc275d414a0c8e19b31c6060cb5568b7) Docs: Update int rule level to string (#13483) (Brandon Mills)
+* [`c8f9c82`](https://github.com/eslint/eslint/commit/c8f9c8210cf4b9da8f07922093d7b219abad9f10) Update: Improve report location no-irregular-whitespace (refs #12334) (#13462) (Milos Djermanovic)
+* [`f2e68ec`](https://github.com/eslint/eslint/commit/f2e68ec1d6cee6299e8a5cdf76c522c11d3008dd) Build: update webpack resolve.mainFields to match website config (#13457) (Milos Djermanovic)
+* [`a96bc5e`](https://github.com/eslint/eslint/commit/a96bc5ec06f3a48bfe458bccd68d4d3b2a280ed9) Fix: arrow-body-style fixer for `in` wrap (fixes #11849) (#13228) (Anix)
+* [`748734f`](https://github.com/eslint/eslint/commit/748734fdd497fbf61f3a616ff4a09169135b9396) Upgrade: Updated puppeteer version to v4.0.0 (#13444) (odidev)
+* [`e951457`](https://github.com/eslint/eslint/commit/e951457b7aaa1b12b135588d36e3f4db4d7b8463) Docs: fix wording in configuring.md (#13469) (Piper)
+* [`0af1d28`](https://github.com/eslint/eslint/commit/0af1d2828d27885483737867653ba1659af72005) Update: add allowSeparatedGroups option to sort-imports (fixes #12951) (#13455) (Milos Djermanovic)
+* [`1050ee7`](https://github.com/eslint/eslint/commit/1050ee78a95da9484ff333dc1c74dac64c05da6f) Update: Improve report location for no-unneeded-ternary (refs #12334) (#13456) (Milos Djermanovic)
+* [`b77b420`](https://github.com/eslint/eslint/commit/b77b4202bd1d5d1306f6f645e88d7a41a51715db) Update: Improve report location for max-len (refs #12334) (#13458) (Milos Djermanovic)
+* [`095194c`](https://github.com/eslint/eslint/commit/095194c0fc0eb02aa69fde6b4280696e0e4de214) Fix: add end location to reports in object-curly-newline (refs #12334) (#13460) (Milos Djermanovic)
+* [`10251bb`](https://github.com/eslint/eslint/commit/10251bbaeba80ac15244f385fc42cf2f2a30e5d2) Fix: add end location to reports in keyword-spacing (refs #12334) (#13461) (Milos Djermanovic)
+* [`2ea7ee5`](https://github.com/eslint/eslint/commit/2ea7ee51a4e05ee76a6dae5954c3b6263b0970a3) Sponsors: Sync README with website (ESLint Jenkins)
+* [`b55fd3b`](https://github.com/eslint/eslint/commit/b55fd3b8c05a29a465a794a524b06c1a28cddf0c) Sponsors: Sync README with website (ESLint Jenkins)
+
+v7.4.0 - July 3, 2020
+
+* [`f21bad2`](https://github.com/eslint/eslint/commit/f21bad2680406a2671b877f8dba47f4475d0cc64) Docs: fix description for `never` in multiline-ternary (fixes #13368) (#13452) (Milos Djermanovic)
+* [`ada2c89`](https://github.com/eslint/eslint/commit/ada2c891298382f82dfabf37cacd59a1057b2bb7) Fix: support typescript generics in arrow-parens (fixes #12570) (#13451) (Milos Djermanovic)
+* [`89ee01e`](https://github.com/eslint/eslint/commit/89ee01e083f1e02293bf8d1447f9b0fdb3cb9384) Fix: Revert config cloning (fixes #13447) (#13449) (薛定谔的猫)
+* [`0a463db`](https://github.com/eslint/eslint/commit/0a463dbf7cc5a77d442879c9117204d4d38db972) Docs: fix no-multiple-empty-lines examples (fixes #13432) (#13433) (Milos Djermanovic)
+* [`ff5317e`](https://github.com/eslint/eslint/commit/ff5317e93425f93cfdf808609551ee67b2032543) Update: Improve array-callback-return report message (#13395) (Philip (flip) Kromer)
+* [`3f51930`](https://github.com/eslint/eslint/commit/3f51930eea7cddc921a9ee3cb0328c7b649c0f83) Fix: false positive new with member in no-extra-parens (fixes #12740) (#13375) (YeonJuan)
+* [`825a5b9`](https://github.com/eslint/eslint/commit/825a5b98d3d84f6eb72b75f7d8519de763cc8898) Fix: Clarify documentation on implicit ignore behavior (fixes #12348) (#12600) (Scott Hardin)
+* [`c139156`](https://github.com/eslint/eslint/commit/c1391566a5f765f25716527de7b5cdee16c0ce36) Sponsors: Sync README with website (ESLint Jenkins)
+* [`0c17e9d`](https://github.com/eslint/eslint/commit/0c17e9d2ac307cc288eea6ed7971bd5a7d33321a) Sponsors: Sync README with website (ESLint Jenkins)
+* [`c680387`](https://github.com/eslint/eslint/commit/c680387ba61f6dccf0390d24a85d871fa83e9fea) Sponsors: Sync README with website (ESLint Jenkins)
+* [`bf3939b`](https://github.com/eslint/eslint/commit/bf3939bbd9a33d0eb96cebe6a53bf61c855f9ba6) Sponsors: Sync README with website (ESLint Jenkins)
+* [`7baf02e`](https://github.com/eslint/eslint/commit/7baf02e983af909800261263f125cca901a5bd0f) Sponsors: Sync README with website (ESLint Jenkins)
+* [`5c4c3fd`](https://github.com/eslint/eslint/commit/5c4c3fdfbda18a13223ad36f44283adbfee8c496) Sponsors: Sync README with website (ESLint Jenkins)
+* [`53912aa`](https://github.com/eslint/eslint/commit/53912aab1856327b399cca26cbb2ba81fd01bfa2) Sponsors: Sync README with website (ESLint Jenkins)
+* [`51e42ec`](https://github.com/eslint/eslint/commit/51e42eca3e87d8259815d736ffe81e604f184057) Update: Add option "ignoreGlobals" to camelcase rule (fixes #11716) (#12782) (David Gasperoni)
+* [`0655f66`](https://github.com/eslint/eslint/commit/0655f66525d167ca1288167b79a77087cfc8fcf6) Update: improve report location in arrow-body-style (refs #12334) (#13424) (YeonJuan)
+* [`d53d69a`](https://github.com/eslint/eslint/commit/d53d69af08cfe55f42e0a0ca725b1014dabccc21) Update: prefer-regex-literal detect regex literals (fixes #12840) (#12842) (Mathias Schreck)
+* [`004adae`](https://github.com/eslint/eslint/commit/004adae3f959414f56e44e5884f6221e9dcda142) Update: rename id-blacklist to id-denylist (fixes #13407) (#13408) (Kai Cataldo)
+
+v7.3.1 - June 22, 2020
+
+* [`de77c11`](https://github.com/eslint/eslint/commit/de77c11e7515f2097ff355ddc0d7b6db9c83c892) Fix: Replace Infinity with Number.MAX_SAFE_INTEGER (fixes #13427) (#13435) (Nicholas C. Zakas)
+
+v7.3.0 - June 19, 2020
+
+* [`638a6d6`](https://github.com/eslint/eslint/commit/638a6d6be18b4a37cfdc7223e1f5acd3718694be) Update: add missing `additionalProperties: false` to some rules' schema (#13198) (Milos Djermanovic)
+* [`949a5cd`](https://github.com/eslint/eslint/commit/949a5cd741c2e930cfb43d80a9b6b084f9d677c3) Update: fix operator-linebreak overrides schema (#13199) (Milos Djermanovic)
+* [`9e1414e`](https://github.com/eslint/eslint/commit/9e1414ee16b8caf582920f8fdf3b6ee1eb0b7cd5) New: Add no-promise-executor-return rule (fixes #12640) (#12648) (Milos Djermanovic)
+* [`09cc0a2`](https://github.com/eslint/eslint/commit/09cc0a2bb5bcf3bcb0766a3c989871f268518437) Update: max-lines reporting loc improvement (refs #12334) (#13318) (Anix)
+* [`ee2fc2e`](https://github.com/eslint/eslint/commit/ee2fc2e90d0f9dfcdba852b0609156bee5280b92) Update: object-property-newline end location (refs #12334) (#13399) (Anix)
+* [`d98152a`](https://github.com/eslint/eslint/commit/d98152a3d8c72e4f5ac4c6fa10a615b12090c8f7) Update: added empty error array check for false negative (#13200) (Anix)
+* [`7fb45cf`](https://github.com/eslint/eslint/commit/7fb45cf13e9908d489bd6d5fba3b7243c01508b9) Fix: clone config before validating (fixes #12592) (#13034) (Anix)
+* [`aed46f6`](https://github.com/eslint/eslint/commit/aed46f69d54da167d9838149954ceeb4b02be5fd) Sponsors: Sync README with website (ESLint Jenkins)
+* [`7686d7f`](https://github.com/eslint/eslint/commit/7686d7feaccc7b8fee927eda6602d641d8de1e5c) Update: semi-spacing should check do-while statements (#13358) (Milos Djermanovic)
+* [`cbd0d00`](https://github.com/eslint/eslint/commit/cbd0d00a1ec2824d7e025bbbc084855ed0bf08bb) Update: disallow multiple options in comma-dangle schema (fixes #13165) (#13166) (Milos Djermanovic)
+* [`b550330`](https://github.com/eslint/eslint/commit/b550330d739c73a7a8f887064e7c911d05a95f9a) New: Add no-unreachable-loop rule (fixes #12381) (#12660) (Milos Djermanovic)
+* [`13999d2`](https://github.com/eslint/eslint/commit/13999d292080f814fa4fb266e011d61c184197c4) Update: curly should check consequent `if` statements (#12947) (Milos Djermanovic)
+* [`c42e548`](https://github.com/eslint/eslint/commit/c42e54893b79b470ca7745bd2a626ffd069e017b) Chore: enable exceptRange option in the yoda rule (#12857) (Milos Djermanovic)
+* [`6cfbd03`](https://github.com/eslint/eslint/commit/6cfbd03b3f22edb4d1c9c61c64eea7c129da71aa) Update: Drop @typescript-eslint/eslint-recommended from `eslint --init` (#13340) (Minh Nguyen)
+* [`796f269`](https://github.com/eslint/eslint/commit/796f269e448fdcbf8a5a62edf1990bd857efd1af) Chore: update eslint-config-eslint's required node version (#13379) (薛定谔的猫)
+* [`9d0186e`](https://github.com/eslint/eslint/commit/9d0186e55bee769ea6aa08dc5a62682f58316412) Docs: Fix changelog versions (#13410) (Tony Brix)
+* [`1ee3c42`](https://github.com/eslint/eslint/commit/1ee3c42ceeee56b650bcc4206ed783b795f65643) Docs: On maxEOF with eol-last (fixes #12742) (#13374) (Arthur Dias)
+* [`2a21049`](https://github.com/eslint/eslint/commit/2a210499288ed14ec9a6fd72decabfb77504c197) Update: key-spacing loc changes for extra space (refs #12334) (#13362) (Anix)
+* [`7ce7988`](https://github.com/eslint/eslint/commit/7ce7988f411da64248a64a9d9d2b7884d5ba39e0) Chore: Replace the inquirer dependency with enquirer (#13254) (Selwyn)
+* [`0f1f5ed`](https://github.com/eslint/eslint/commit/0f1f5ed2a20b8fb575d4360316861cf4c2b9b7bc) Docs: Add security policy link to README (#13403) (Nicholas C. Zakas)
+* [`9e9ba89`](https://github.com/eslint/eslint/commit/9e9ba897c566601cfe90522099c635ea316b235f) Sponsors: Sync README with website (ESLint Jenkins)
+* [`ca59fb9`](https://github.com/eslint/eslint/commit/ca59fb95a395c0a02ed23768a70e086480ab1f6d) Sponsors: Sync README with website (ESLint Jenkins)
+
 v7.2.0 - June 5, 2020
 
 * [`b735a48`](https://github.com/eslint/eslint/commit/b735a485e77bcc791e4c4c6b8716801d94e98b2c) Update: add enforceForFunctionPrototypeMethods option to no-extra-parens (#12895) (Milos Djermanovic)
@@ -17,6 +240,7 @@ v7.2.0 - June 5, 2020
 * [`ee30e5d`](https://github.com/eslint/eslint/commit/ee30e5d8bb1a4c82a2a3fbe1b9ee9f979b55c5c4) Sponsors: Sync README with website (ESLint Jenkins)
 * [`c29bd9f`](https://github.com/eslint/eslint/commit/c29bd9f75582e5b1a403a8ffd0aafd1ffc8c58e1) Chore: Add breaking/core change link to issue templates (#13344) (Kai Cataldo)
 * [`d55490f`](https://github.com/eslint/eslint/commit/d55490fa73ff69416de375e4c1cd67b6edba531c) Sponsors: Sync README with website (ESLint Jenkins)
+
 v7.1.0 - May 22, 2020
 
 * [`a93083a`](https://github.com/eslint/eslint/commit/a93083af89c6f9714dcdd4a7f27c8655a0b0dba6) Fix: astUtils.getNextLocation returns invalid location after CRLF (#13275) (Milos Djermanovic)
@@ -46,6 +270,7 @@ v7.1.0 - May 22, 2020
 * [`f44a6b4`](https://github.com/eslint/eslint/commit/f44a6b4fd92602af8e2c75d5852f796ec064aa8e) Chore: fix invalid syntax in require-await tests (#13277) (Milos Djermanovic)
 * [`2c778fb`](https://github.com/eslint/eslint/commit/2c778fb6e31b7943bb27a47a6e15dcbfd8336f39) Fix: remove custom plugins from replacedBy metadata (#13274) (Kai Cataldo)
 * [`0db3b1d`](https://github.com/eslint/eslint/commit/0db3b1d5cc5e4e1de21462679581b7a4d89ff36e) Sponsors: Sync README with website (ESLint Jenkins)
+
 v7.0.0 - May 8, 2020
 
 * [`b98d8bd`](https://github.com/eslint/eslint/commit/b98d8bda4630fe8278c5aa2b6650630770568fe5) Upgrade: eslint-release@2.0.0 (#13271) (Kai Cataldo)
@@ -256,6 +481,7 @@ v7.0.0 - May 8, 2020
 * [`39f5a45`](https://github.com/eslint/eslint/commit/39f5a453579b2ad732212edeb71f84ecb0991f97) Chore: add test cases for for-direction (#12698) (YeonJuan)
 * [`b340304`](https://github.com/eslint/eslint/commit/b3403045e535921df6d34785a4ce053e14ba27fd) Chore: Add extra test, improve docs (#12492) (Kevin Partington)
 * [`827259e`](https://github.com/eslint/eslint/commit/827259ea009f98a0fdf3f7ebf1bfb6cd661ce28d) Build: package.json update for eslint-config-eslint release (ESLint Jenkins)
+
 v7.0.0-rc.0 - April 24, 2020
 
 * [`0b1d65a`](https://github.com/eslint/eslint/commit/0b1d65a45aa5dfe08cd596c420490e81b546317e) Update: Improve report location for array-callback-return (refs #12334) (#13109) (Milos Djermanovic)
index f5f3fd589ee7d2f9e7a956b643c4b355e4eb759e..840a51a86d7253f0a3be10fe25efdc46aecc6fbd 100644 (file)
@@ -550,7 +550,7 @@ target.mocha = () => {
         errors++;
     }
 
-    lastReturn = exec(`${getBinFile("nyc")} check-coverage --statement 99 --branch 98 --function 99 --lines 99`);
+    lastReturn = exec(`${getBinFile("nyc")} check-coverage --statement 98 --branch 97 --function 98 --lines 98`);
     if (lastReturn.code !== 0) {
         errors++;
     }
index 0cb3963b2c0b22b9349613d79fc26f91847ed445..7740b392f7c23e7e3a7d3d0cfb098d9b70709080 100644 (file)
@@ -1,4 +1,4 @@
-[![NPM version](https://img.shields.io/npm/v/eslint.svg)](https://www.npmjs.com/package/eslint)
+[![npm version](https://img.shields.io/npm/v/eslint.svg)](https://www.npmjs.com/package/eslint)
 [![Downloads](https://img.shields.io/npm/dm/eslint.svg)](https://www.npmjs.com/package/eslint)
 [![Build Status](https://github.com/eslint/eslint/workflows/CI/badge.svg)](https://github.com/eslint/eslint/actions)
 [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint.svg?type=shield)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint?ref=badge_shield)
@@ -33,11 +33,12 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J
 4. [Filing Issues](#filing-issues)
 5. [Frequently Asked Questions](#faq)
 6. [Releases](#releases)
-7. [Semantic Versioning Policy](#semantic-versioning-policy)
-8. [License](#license)
-9. [Team](#team)
-10. [Sponsors](#sponsors)
-11. [Technology Sponsors](#technology-sponsors)
+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)
 
 ## <a name="installation-and-usage"></a>Installation and Usage
 
@@ -121,7 +122,7 @@ Yes, ESLint natively supports parsing JSX syntax (this must be enabled in [confi
 
 ### What ECMAScript versions does ESLint support?
 
-ESLint has full support for ECMAScript 3, 5 (default), 2015, 2016, 2017, 2018, and 2019. You can set your desired ECMAScript syntax (and other settings, like global variables or your target environments) through [configuration](https://eslint.org/docs/user-guide/configuring).
+ESLint has full support for ECMAScript 3, 5 (default), 2015, 2016, 2017, 2018, 2019, and 2020. You can set your desired ECMAScript syntax (and other settings, like global variables or your target environments) through [configuration](https://eslint.org/docs/user-guide/configuring).
 
 ### What about experimental features?
 
@@ -139,32 +140,41 @@ Join our [Mailing List](https://groups.google.com/group/eslint) or [Chatroom](ht
 
 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.
 
+## <a name="security-policy"></a>Security Policy
+
+ESLint takes security seriously. We work hard to ensure that ESLint is safe for everyone and that security issues are addressed quickly and responsibly. Read the full [security policy](https://github.com/eslint/.github/blob/master/SECURITY.md).
+
 ## <a name="semantic-versioning-policy"></a>Semantic Versioning Policy
 
 ESLint follows [semantic versioning](https://semver.org). However, due to the nature of ESLint as a code quality tool, it's not always clear when a minor or major version bump occurs. To help clarify this for everyone, we've defined the following semantic versioning policy for ESLint:
 
 * Patch release (intended to not break your lint build)
-    * A bug fix in a rule that results in ESLint reporting fewer errors.
+    * A bug fix in a rule that results in ESLint reporting fewer linting errors.
     * A bug fix to the CLI or core (including formatters).
     * Improvements to documentation.
     * Non-user-facing changes such as refactoring code, adding, deleting, or modifying tests, and increasing test coverage.
     * Re-releasing after a failed release (i.e., publishing a release that doesn't work for anyone).
 * Minor release (might break your lint build)
-    * A bug fix in a rule that results in ESLint reporting more errors.
+    * A bug fix in a rule that results in ESLint reporting more linting errors.
     * A new rule is created.
-    * A new option to an existing rule that does not result in ESLint reporting more errors by default.
+    * A new option to an existing rule that does not result in ESLint reporting more linting errors by default.
     * An existing rule is deprecated.
     * A new CLI capability is created.
     * New capabilities to the public API are added (new classes, new methods, new arguments to existing methods, etc.).
     * A new formatter is created.
-    * `eslint:recommended` is updated and will result in strictly fewer errors (e.g., rule removals).
+    * `eslint:recommended` is updated and will result in strictly fewer linting errors (e.g., rule removals).
 * Major release (likely to break your lint build)
-    * `eslint:recommended` is updated and may result in new errors (e.g., rule additions, most rule option updates).
-    * A new option to an existing rule that results in ESLint reporting more errors by default.
+    * `eslint:recommended` is updated and may result in new linting errors (e.g., rule additions, most rule option updates).
+    * A new option to an existing rule that results in ESLint reporting more linting errors by default.
     * An existing formatter is removed.
-    * Part of the public API is removed or changed in an incompatible way.
+    * Part of the public API is removed or changed in an incompatible way. The public API includes:
+        * Rule schemas
+        * Configuration schema
+        * Command-line options
+        * Node.js API
+        * Rule, formatter, parser, plugin APIs
 
-According to our policy, any minor update may report more 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.
+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="license"></a>License
 
@@ -201,6 +211,11 @@ Toru Nagashima
 <img src="https://github.com/kaicataldo.png?s=75" width="75" height="75"><br />
 Kai Cataldo
 </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
+</a>
 </td></tr></tbody></table>
 
 
@@ -213,11 +228,6 @@ The people who review and implement new features.
 <img src="https://github.com/aladdin-add.png?s=75" width="75" height="75"><br />
 薛定谔的猫
 </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
-</a>
 </td></tr></tbody></table>
 
 
@@ -233,6 +243,11 @@ The people who review and fix bugs and help triage issues.
 Pig Fang
 </a>
 </td><td align="center" valign="top" width="11%">
+<a href="https://github.com/anikethsaha">
+<img src="https://github.com/anikethsaha.png?s=75" width="75" height="75"><br />
+Anix
+</a>
+</td><td align="center" valign="top" width="11%">
 <a href="https://github.com/yeonjuan">
 <img src="https://github.com/yeonjuan.png?s=75" width="75" height="75"><br />
 YeonJuan
@@ -248,12 +263,15 @@ The following companies, organizations, and individuals support ESLint's ongoing
 
 <!-- NOTE: This section is autogenerated. Do not manually edit.-->
 <!--sponsorsstart-->
-<h3>Gold Sponsors</h3>
-<p><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></p><h3>Silver Sponsors</h3>
+<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://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://bruce.agency"><img src="https://images.opencollective.com/brucemade/0c70c59/logo.png" alt="Bruce" height="32"></a> <a href="https://edubirdie.com/"><img src="https://images.opencollective.com/edubirdie2/b1d51ab/logo.png" alt="EduBirdie" height="32"></a> <a href="https://www.casinotop.com/"><img src="https://images.opencollective.com/casinotop-com/10fd95b/logo.png" alt="CasinoTop.com" height="32"></a> <a href="https://www.casinotopp.net/"><img src="https://images.opencollective.com/casino-topp/1dd399a/logo.png" alt="Casino Topp" height="32"></a> <a href="https://writersperhour.com/write-my-essay"><img src="https://images.opencollective.com/writersperhour/5787d4b/logo.png" alt="Writers Per Hour" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="https://cooltechzone.com/netflix-vpn"><img src="https://images.opencollective.com/vpn-netflix/4850160/logo.png" alt="vpn netflix" height="32"></a> <a href="https://www.kasinot.fi"><img src="https://images.opencollective.com/kasinot-fi/e09aa2e/logo.png" alt="Kasinot.fi" height="32"></a> <a href="https://www.pelisivut.com"><img src="https://images.opencollective.com/pelisivut/04f08f2/logo.png" alt="Pelisivut" height="32"></a> <a href="https://www.nettikasinot.org"><img src="https://images.opencollective.com/nettikasinot-org/bbd887f/logo.png" alt="Nettikasinot.org" height="32"></a> <a href="https://www.bonus.com.de/freispiele"><img src="https://images.opencollective.com/bonusfinder-deutschland/646169e/logo.png" alt="BonusFinder Deutschland" height="32"></a> <a href="https://www.bugsnag.com/platforms?utm_source=Open Collective&utm_medium=Website&utm_content=open-source&utm_campaign=2019-community&utm_term="><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/0b37d14/logo.png" alt="Free Icons by Icons8" 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://tekhattan.com"><img src="https://images.opencollective.com/tekhattan/bc73c28/logo.png" alt="TekHattan" height="32"></a> <a href="https://www.marfeel.com/"><img src="https://images.opencollective.com/marfeel/4b88e30/logo.png" alt="Marfeel" height="32"></a> <a href="http://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://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/printable-calendar"><img src="https://images.opencollective.com/betacalendars/9334b33/logo.png" alt="2021 calendar" height="32"></a> <a href="https://buy.fineproxy.org/eng/"><img src="https://images.opencollective.com/buy-fineproxy-org/2002c40/logo.png" alt="Buy.Fineproxy.Org" height="32"></a> <a href="https://www.veikkaajat.com"><img src="https://images.opencollective.com/veikkaajat/b92b427/logo.png" alt="Veikkaajat.com" 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.marfeel.com/"><img src="https://images.opencollective.com/marfeel/4b88e30/logo.png" alt="Marfeel" 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
 
 * Site search ([eslint.org](https://eslint.org)) is sponsored by [Algolia](https://www.algolia.com)
+* Hosting for ([eslint.org](https://eslint.org)) is sponsored by [Netlify](https://www.netlify.com)
+* Password management is sponsored by [1Password](https://www.1password.com)
index 712fc4235ecab2d12f6ebefdcd5c1354410317b9..b83f6578832df91536329f30ee222e112f7c4dca 100644 (file)
@@ -1,3 +1,15 @@
+/*
+ * STOP!!! DO NOT MODIFY.
+ *
+ * This file is part of the ongoing work to move the eslintrc-style config
+ * system into the @eslint/eslintrc package. This file needs to remain
+ * unchanged in order for this work to proceed.
+ *
+ * If you think you need to change this file, please contact @nzakas first.
+ *
+ * Thanks in advance for your cooperation.
+ */
+
 /**
  * @fileoverview Defines a schema for configs.
  * @author Sylvan Mably
diff --git a/eslint/conf/environments.js b/eslint/conf/environments.js
deleted file mode 100644 (file)
index 90589b1..0000000
+++ /dev/null
@@ -1,168 +0,0 @@
-/**
- * @fileoverview Defines environment settings and globals.
- * @author Elan Shanker
- */
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const globals = require("globals");
-
-//------------------------------------------------------------------------------
-// Helpers
-//------------------------------------------------------------------------------
-
-/**
- * Get the object that has difference.
- * @param {Record<string,boolean>} current The newer object.
- * @param {Record<string,boolean>} prev The older object.
- * @returns {Record<string,boolean>} The difference object.
- */
-function getDiff(current, prev) {
-    const retv = {};
-
-    for (const [key, value] of Object.entries(current)) {
-        if (!Object.hasOwnProperty.call(prev, key)) {
-            retv[key] = value;
-        }
-    }
-
-    return retv;
-}
-
-const newGlobals2015 = getDiff(globals.es2015, globals.es5); // 19 variables such as Promise, Map, ...
-const newGlobals2017 = {
-    Atomics: false,
-    SharedArrayBuffer: false
-};
-const newGlobals2020 = {
-    BigInt: false,
-    BigInt64Array: false,
-    BigUint64Array: false,
-    globalThis: false
-};
-
-//------------------------------------------------------------------------------
-// Public Interface
-//------------------------------------------------------------------------------
-
-/** @type {Map<string, import("../lib/shared/types").Environment>} */
-module.exports = new Map(Object.entries({
-
-    // Language
-    builtin: {
-        globals: globals.es5
-    },
-    es6: {
-        globals: newGlobals2015,
-        parserOptions: {
-            ecmaVersion: 6
-        }
-    },
-    es2015: {
-        globals: newGlobals2015,
-        parserOptions: {
-            ecmaVersion: 6
-        }
-    },
-    es2017: {
-        globals: { ...newGlobals2015, ...newGlobals2017 },
-        parserOptions: {
-            ecmaVersion: 8
-        }
-    },
-    es2020: {
-        globals: { ...newGlobals2015, ...newGlobals2017, ...newGlobals2020 },
-        parserOptions: {
-            ecmaVersion: 11
-        }
-    },
-
-    // Platforms
-    browser: {
-        globals: globals.browser
-    },
-    node: {
-        globals: globals.node,
-        parserOptions: {
-            ecmaFeatures: {
-                globalReturn: true
-            }
-        }
-    },
-    "shared-node-browser": {
-        globals: globals["shared-node-browser"]
-    },
-    worker: {
-        globals: globals.worker
-    },
-    serviceworker: {
-        globals: globals.serviceworker
-    },
-
-    // Frameworks
-    commonjs: {
-        globals: globals.commonjs,
-        parserOptions: {
-            ecmaFeatures: {
-                globalReturn: true
-            }
-        }
-    },
-    amd: {
-        globals: globals.amd
-    },
-    mocha: {
-        globals: globals.mocha
-    },
-    jasmine: {
-        globals: globals.jasmine
-    },
-    jest: {
-        globals: globals.jest
-    },
-    phantomjs: {
-        globals: globals.phantomjs
-    },
-    jquery: {
-        globals: globals.jquery
-    },
-    qunit: {
-        globals: globals.qunit
-    },
-    prototypejs: {
-        globals: globals.prototypejs
-    },
-    shelljs: {
-        globals: globals.shelljs
-    },
-    meteor: {
-        globals: globals.meteor
-    },
-    mongo: {
-        globals: globals.mongo
-    },
-    protractor: {
-        globals: globals.protractor
-    },
-    applescript: {
-        globals: globals.applescript
-    },
-    nashorn: {
-        globals: globals.nashorn
-    },
-    atomtest: {
-        globals: globals.atomtest
-    },
-    embertest: {
-        globals: globals.embertest
-    },
-    webextensions: {
-        globals: globals.webextensions
-    },
-    greasemonkey: {
-        globals: globals.greasemonkey
-    }
-}));
index a22ea5d90a269422b5265b9f9000a54874297c55..e341083febced0a3db9e54ab05210e2a5140b97d 100644 (file)
@@ -4,11 +4,11 @@ One of the great things about open source projects is that anyone can contribute
 
 This guide is intended for anyone who wants to contribute to an ESLint project. Please read it carefully as it answers a lot of the questions many newcomers have when first working with our projects.
 
-## Read the [Code of Conduct](https://js.foundation/community/code-of-conduct)
+## Read the [Code of Conduct](https://eslint.org/conduct)
 
-ESLint welcomes contributions from everyone and adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct). We kindly request that you read over our code of conduct before contributing.
+ESLint welcomes contributions from everyone and adheres to the [OpenJS Foundation Code of Conduct](https://eslint.org/conduct). We kindly request that you read over our code of conduct before contributing.
 
-## [Signing the CLA](https://js.foundation/CLA)
+## [Signing the CLA](https://openjsf.org/about/the-openjs-foundation-cla/)
 
 In order to submit code or documentation to an ESLint project, you will need to electronically sign our [Contributor License Agreement](https://cla.js.foundation/eslint/eslint). The CLA is you giving us permission to use your contribution.
 
index a93e1efbc24c3c62ab57d99b11131dd041a37abb..a1fc071118e64c833eef464fe42e9365ab8bab65 100644 (file)
@@ -6,7 +6,7 @@ ESLint has a very lightweight development environment that makes updating code f
 
 Go to <https://nodejs.org/> to download and install the latest stable version for your operating system.
 
-Most of the installers come with [npm](https://www.npmjs.com/) already installed, but if for some reason it doesn't work on your system, you can install it manually using the instructions on the site.
+Most of the installers already come with [npm](https://www.npmjs.com/) but if for some reason npm doesn't work on your system, you can install it manually using the instructions on the site.
 
 ## Step 2: Fork and checkout your own ESLint repository
 
@@ -23,7 +23,7 @@ You must be connected to the Internet for this step to work. You'll see a lot of
 
 ## Step 3: Add the upstream source
 
-The *upstream source* is the main ESLint repository that active development happens on. While you won't have push access to upstream, you will have pull access, allowing you to pull in the latest code whenever you want.
+The *upstream source* is the main ESLint repository where active development happens. While you won't have push access to upstream, you will have pull access, allowing you to pull in the latest code whenever you want.
 
 To add the upstream source for ESLint, run the following in your repository:
 
index 991a1cbcd26ee706f7b38d9b97b7266beba3fd74..cfa03721876243c9b444a4ce7d4d37d0259c3a0a 100644 (file)
@@ -8,18 +8,18 @@ While ESLint is designed to be run on the command line, it's possible to use ESL
 
 * [ESLint]
     * [constructor()][eslint-constructor]
-    * [lintFiles()][eslint-lintFiles]
-    * [lintText()][eslint-lintText]
-    * [calculateConfigForFile()][eslint-calculateConfigForFile]
-    * [isPathIgnored()][eslint-isPathIgnored]
-    * [loadFormatter()][eslint-loadFormatter]
+    * [lintFiles()][eslint-lintfiles]
+    * [lintText()][eslint-linttext]
+    * [calculateConfigForFile()][eslint-calculateconfigforfile]
+    * [isPathIgnored()][eslint-ispathignored]
+    * [loadFormatter()][eslint-loadformatter]
     * [static version][eslint-version]
-    * [static outputFixes()][eslint-outputFixes]
-    * [static getErrorResults()][eslint-getErrorResults]
-    * [LintResult type](lintresult)
-    * [LintMessage type](lintmessage)
-    * [EditInfo type](editinfo)
-    * [Formatter type](formatter)
+    * [static outputFixes()][eslint-outputfixes]
+    * [static getErrorResults()][eslint-geterrorresults]
+    * [LintResult type][lintresult]
+    * [LintMessage type][lintmessage]
+    * [EditInfo type][editinfo]
+    * [Formatter type][formatter]
 * [SourceCode](#sourcecode)
     * [splitLines()](#sourcecode-splitlines)
 * [Linter](#linter)
@@ -926,7 +926,7 @@ The top-level report object has a `results` array containing all linting results
 * `source` - The source code for the given file. This property is omitted if this file has no errors/warnings or if the `output` property is present.
 * `output` - The source code for the given file with as many fixes applied as possible, so you can use that to rewrite the files if necessary. This property is omitted if no fix is available.
 
-The top-level report object also has `errorCount` and `warningCount` which give the exact number of errors and warnings respectively on all the files. Additionally, `usedDeprecatedRules` signals any deprecated rules used and their replacement (if available). Specifically, it is array of objects with properties like so:
+The top-level report object also has `errorCount` and `warningCount` which give the exact number of errors and warnings respectively on all the files. Additionally, `usedDeprecatedRules` signals any deprecated rules used and their replacement (if available). Specifically, it is an array of objects with properties like so:
 
 * `ruleId` - The name of the rule (e.g. `indent-legacy`).
 * `replacedBy` - An array of rules that replace the deprecated rule (e.g. `["indent"]`).
@@ -1383,14 +1383,14 @@ ruleTester.run("my-rule", myRule, {
 [thirdparty-formatters]: https://www.npmjs.com/search?q=eslintformatter
 [eslint]: #eslint-class
 [eslint-constructor]: #-new-eslintoptions
-[eslint-lintfiles]: #-eslintlintFilespatterns
-[eslint-linttext]: #-eslintlintTextcode-options
-[eslint-calculateconfigforfile]: #-eslintcalculateConfigForFilefilePath
-[eslint-ispathignored]: #-eslintisPathIgnoredfilePath
-[eslint-loadformatter]: #-eslintloadFormatternameOrPath
+[eslint-lintfiles]: #-eslintlintfilespatterns
+[eslint-linttext]: #-eslintlinttextcode-options
+[eslint-calculateconfigforfile]: #-eslintcalculateconfigforfilefilepath
+[eslint-ispathignored]: #-eslintispathignoredfilepath
+[eslint-loadformatter]: #-eslintloadformatternameorpath
 [eslint-version]: #-eslintversion
-[eslint-outputfixes]: #-eslintoutputFixesresults
-[eslint-geterrorresults]: #-eslintgetErrorResultsresults
+[eslint-outputfixes]: #-eslintoutputfixesresults
+[eslint-geterrorresults]: #-eslintgeterrorresultsresults
 [lintresult]: #-lintresult-type
 [lintmessage]: #-lintmessage-type
 [editinfo]: #-editinfo-type
index a7e9434cdbbbce5e780089a6fafe49191c72e0ff..a6b7fa5b1d7271281311eb6ba2552045d19d39a8 100644 (file)
@@ -22,7 +22,7 @@ Once you have a local copy and have Node.JS and npm installed, you'll need to in
 
 Now when you run `eslint`, it will be running your local copy and showing your changes.
 
-**Note:** It's a good idea to re-rerun `npm install` whenever you pull from the main repository to ensure you have the latest development dependencies.
+**Note:** It's a good idea to re-run `npm install` whenever you pull from the main repository to ensure you have the latest development dependencies.
 
 ## Directory structure
 
index 8cb6738e82888c1f5336abf6f371a15fbd617e39..7efed41933505b083f413235280e3cf0dd9d0dba 100644 (file)
@@ -11,7 +11,7 @@ module.exports = function(results) {
 };
 ```
 
-To run ESLint with this formatter, you can use the `-f` (or `--formatter`) command line flag:
+To run ESLint with this formatter, you can use the `-f` (or `--format`) command line flag:
 
 ```bash
 eslint -f ./my-awesome-formatter.js src/
@@ -50,7 +50,7 @@ The [Using Rule metadata](#using-rule-metadata) example shows how to use the `da
 
 ## Packaging the Custom Formatter
 
-Custom formatters can also be distributed through npm packages. To do so, create an npm package with a name in the format of `eslint-formatter-*`, where `*` is the name of your formatter (such as `eslint-formatter-awesome`). Projects should then install the package and can use the custom formatter with the `-f` (or `--formatter`) flag like this:
+Custom formatters can also be distributed through npm packages. To do so, create an npm package with a name in the format of `eslint-formatter-*`, where `*` is the name of your formatter (such as `eslint-formatter-awesome`). Projects should then install the package and can use the custom formatter with the `-f` (or `--format`) flag like this:
 
 ```bash
 eslint -f awesome src/
index a2b238cc9362c8a7394fc17d1a4c8201ed4ecc89..521b9eb5aacf1d712a583c7908d1d12aadf12412 100644 (file)
@@ -180,7 +180,7 @@ module.exports = {
             env: ["node"],
             rules: {
                 "myPlugin/my-rule": "off",
-                "eslint-plugin-myPlugin/another-rule": "off"
+                "eslint-plugin-myPlugin/another-rule": "off",
                 "eslint-plugin-myPlugin/yet-another-rule": "error"
             }
         }
index 17058b4058af92d6c96f489ed4799b4c2d85358f..89d37c483a4f03b53b68eb6da2c378712c06267c 100644 (file)
@@ -68,7 +68,7 @@ The source file for a rule exports an object with the following properties.
 
 * `fixable` (string) is either `"code"` or `"whitespace"` if the `--fix` option on the [command line](../user-guide/command-line-interface.md#fix) automatically fixes problems reported by the rule
 
-    **Important:** Without the `fixable` property, ESLint does not [apply fixes](#applying-fixes) even if the rule implements `fix` functions. Omit the `fixable` property if the rule is not fixable.
+    **Important:** the `fixable` property is mandatory for fixable rules. If this property isn't specified, ESLint will throw an error whenever the rule attempts to produce a fix. Omit the `fixable` property if the rule is not fixable.
 
 * `schema` (array) specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../user-guide/configuring.md#configuring-rules)
 
@@ -301,7 +301,7 @@ context.report({
 
 Here, the `fix()` function is used to insert a semicolon after the node. Note that a fix is not immediately applied, and may not be applied at all if there are conflicts with other fixes. After applying fixes, ESLint will run all of the enabled rules again on the fixed code, potentially applying more fixes. This process will repeat up to 10 times, or until no more fixable problems are found. Afterwards, any remaining problems will be reported as usual.
 
-**Important:** Unless the rule [exports](#rule-basics) the `meta.fixable` property, ESLint does not apply fixes even if the rule implements `fix` functions.
+**Important:** The `meta.fixable` property is mandatory for fixable rules. ESLint will throw an error if a rule that implements `fix` functions does not [export](#rule-basics) the `meta.fixable` property.
 
 The `fixer` object has the following methods:
 
@@ -375,13 +375,15 @@ 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 semicolumns. 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 confirm 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:
 
 1. Don't try to do too much and suggest large refactors that could introduce a lot of breaking changes.
 1. As noted above, don't try to conform to user-defined styles.
 
+Suggestions are intended to provide fixes. ESLint will automatically remove the whole suggestion from the linting output if the suggestion's `fix` function returned `null` or an empty array/sequence.
+
 #### Suggestion `messageId`s
 
 Instead of using a `desc` key for suggestions a `messageId` can be used instead. This works the same way as `messageId`s for the overall error (see [messageIds](#messageIds)). Here is an example of how to use it in a rule:
@@ -733,5 +735,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 `1` or `2` in the configuration file.
+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.
 3. Run the [command line interface](../user-guide/command-line-interface.md) using the `--rulesdir` option to specify the location of your runtime rules.
diff --git a/eslint/docs/maintainer-guide/npm-2fa.md b/eslint/docs/maintainer-guide/npm-2fa.md
deleted file mode 100644 (file)
index e9a5177..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-# npm two-factor authentication
-
-The `eslint` npm account has two-factor authentication (2FA) enabled. The 2FA secret is distributed using a team on [Keybase](https://keybase.io). Anyone doing a release of a package from the Jenkins server needs to have access to the 2FA secret.
-
-If you're on ESLint's TSC, you should perform the following steps to obtain the 2FA secret:
-
-1. Download the [Keybase app](https://keybase.io/download) on a smartphone.
-1. Open the app and create an account.
-1. From the app, link your Keybase username with your GitHub username. (At the time of writing, the UI for this is to tap the face icon in the bottom-left of the app, then the profile picture in the top-right, then tap "Prove your GitHub" and follow the instructions.)
-1. Mention your Keybase username in the team chatroom, and wait for someone to add you to the Keybase team.
-1. Download an authenticator app like [Google Authenticator](https://support.google.com/accounts/answer/1066447) or [Authy](https://authy.com/), if you don't have one installed already.
-1. In the Keybase app, navigate to the Keybase filesystem (at the time of writing, the UI for this is to tap the hamburger icon in the bottom-right, then tap "Files") and then navigate to `/team/eslint/auth`.
-    * If your authenticator app is downloaded on the same device as your Keybase app (this will usually be the case if you're using the Keybase mobile app), then open `npm_2fa_code.txt` and copy the contents to the clipboard. Open your authenticator app, and paste the contents as a new key (by selecting something like "Enter a provided key" or "Enter key manually").
-    * If your authenticator app is downloaded on a *different* device from your Keybase app (e.g. if you're using a Keybase desktop app), then open `npm_2fa_code.png` and scan it as a QR code from your authenticator app.
-
-You should now be able to generate 6-digit 2FA codes for the `eslint` npm account using your authenticator app.
index 94e1780c79bedcbfa8798311fab51cb7fef0ad6a..d406475bed3000273aad68bdb4b7b947cababc71 100644 (file)
@@ -33,7 +33,7 @@ On the day of a scheduled release, the release team should follow these steps:
     * Small bugfixes written by a team member.
 1. Log into Jenkins and schedule a build for the "ESLint Release" job.
 1. Watch the console output of the build on Jenkins. At some point, the build will pause and a link will be produced with an input field for a six-digit 2FA code.
-1. Enter the current six-digit 2FA code from your authenticator app. (Also see: [npm-2fa](./npm-2fa))
+1. Enter the current six-digit 2FA code from your authenticator app.
 1. Continue the build and wait for it to finish.
 1. Update the release blog post with a "Highlights" section, including new rules and anything else that's important.
 1. Make a release announcement in the public chatroom.
index 96135f8c93267a6dc458b427e5532806b884b228..164dfab7c46bacb07c1c94ecd44d94181a9f73fc 100644 (file)
@@ -16,6 +16,8 @@ This rule has an object option:
 * `"ignoreDestructuring": true` does not check destructured identifiers (but still checks any use of those identifiers later in the code)
 * `"ignoreImports": false` (default) enforces camelcase style for ES2015 imports
 * `"ignoreImports": true` does not check ES2015 imports (but still checks any use of the imports later in the code except function arguments)
+* `"ignoreGlobals": false` (default) enforces camelcase style for global variables
+* `"ignoreGlobals": true` does not enforce camelcase style for global variables
 * `allow` (`string[]`) list of properties to accept. Accept regex.
 
 ### properties: "always"
@@ -217,6 +219,28 @@ Examples of **correct** code for this rule with the `{ "ignoreImports": true }`
 import { snake_cased } from 'mod';
 ```
 
+### ignoreGlobals: false
+
+Examples of **incorrect** code for this rule with the default `{ "ignoreGlobals": false }` option:
+
+```js
+/*eslint camelcase: ["error", {ignoreGlobals: false}]*/
+/* global no_camelcased */
+
+const foo = no_camelcased;
+```
+
+### ignoreGlobals: true
+
+Examples of **correct** code for this rule with the `{ "ignoreGlobals": true }` option:
+
+```js
+/*eslint camelcase: ["error", {ignoreGlobals: true}]*/
+/* global no_camelcased */
+
+const foo = no_camelcased;
+```
+
 ## allow
 
 Examples of **correct** code for this rule with the `allow` option:
index ffa2bfd117edb6e72a3d0a9318db7820e072a813..23107719a32522453f8cf5db29d7a5ce1603533b 100644 (file)
@@ -113,7 +113,7 @@ If your project will not be following a consistent comma-spacing pattern, turn t
 
 ## Further Reading
 
-* [Javascript](http://javascript.crockford.com/code.html)
+* [JavaScript](http://javascript.crockford.com/code.html)
 * [Dojo Style Guide](https://dojotoolkit.org/reference-guide/1.9/developer/styleguide.html)
 
 
index a7685c4c2f2162690158660bc9512b894298099d..87a7d9503bbdac12dc7d759983fbb574444230d5 100644 (file)
@@ -1,82 +1,3 @@
 # disallow specified identifiers (id-blacklist)
 
-> "There are only two hard things in Computer Science: cache invalidation and naming things." — Phil Karlton
-
-Bad names can lead to hard-to-decipher code. Generic names, such as `data`, don't infer much about the code and the values it receives. This rule allows you to configure a blacklist of bad identifier names, that you don't want to see in your code.
-
-## Rule Details
-
-This rule disallows specified identifiers in assignments and `function` definitions.
-
-This rule will catch blacklisted identifiers that are:
-
-- variable declarations
-- function declarations
-- object properties assigned to during object creation
-
-It will not catch blacklisted identifiers that are:
-
-- function calls (so you can still use functions you do not have control over)
-- object properties (so you can still use objects you do not have control over)
-
-## Options
-
-The rule takes one or more strings as options: the names of restricted identifiers.
-
-For example, to restrict the use of common generic identifiers:
-
-```json
-{
-    "id-blacklist": ["error", "data", "err", "e", "cb", "callback"]
-}
-```
-
-Examples of **incorrect** code for this rule with sample `"data", "callback"` restricted identifiers:
-
-```js
-/*eslint id-blacklist: ["error", "data", "callback"] */
-
-var data = {...};
-
-function callback() {
-    // ...
-}
-
-element.callback = function() {
-    // ...
-};
-
-var itemSet = {
-    data: [...]
-};
-```
-
-Examples of **correct** code for this rule with sample `"data", "callback"` restricted identifiers:
-
-```js
-/*eslint id-blacklist: ["error", "data", "callback"] */
-
-var encodingOptions = {...};
-
-function processFileResult() {
-    // ...
-}
-
-element.successHandler = function() {
-    // ...
-};
-
-var itemSet = {
-    entities: [...]
-};
-
-callback(); // all function calls are ignored
-
-foo.callback(); // all function calls are ignored
-
-foo.data; // all property names that are not assignments are ignored
-```
-
-## When Not To Use It
-
-You can turn this rule off if you are happy for identifiers to be named freely.
+This rule was **deprecated** in ESLint v7.5.0 and replaced by the [id-denylist](id-denylist.md) rule.
diff --git a/eslint/docs/rules/id-denylist.md b/eslint/docs/rules/id-denylist.md
new file mode 100644 (file)
index 0000000..040f26e
--- /dev/null
@@ -0,0 +1,82 @@
+# disallow specified identifiers (id-denylist)
+
+> "There are only two hard things in Computer Science: cache invalidation and naming things." — Phil Karlton
+
+Generic names can lead to hard-to-decipher code. This rule allows you to specify a deny list of disallowed identifier names to avoid this practice.
+
+## Rule Details
+
+This rule disallows specified identifiers in assignments and `function` definitions.
+
+This rule will catch disallowed identifiers that are:
+
+- variable declarations
+- function declarations
+- object properties assigned to during object creation
+
+It will not catch disallowed identifiers that are:
+
+- function calls (so you can still use functions you do not have control over)
+- object properties (so you can still use objects you do not have control over)
+
+## Options
+
+The rule takes one or more strings as options: the names of restricted identifiers.
+
+For example, to restrict the use of common generic identifiers:
+
+```json
+{
+    "id-denylist": ["error", "data", "err", "e", "cb", "callback"]
+}
+```
+
+Examples of **incorrect** code for this rule with sample `"data", "callback"` restricted identifiers:
+
+```js
+/*eslint id-denylist: ["error", "data", "callback"] */
+
+var data = {...};
+
+function callback() {
+    // ...
+}
+
+element.callback = function() {
+    // ...
+};
+
+var itemSet = {
+    data: [...]
+};
+```
+
+Examples of **correct** code for this rule with sample `"data", "callback"` restricted identifiers:
+
+```js
+/*eslint id-denylist: ["error", "data", "callback"] */
+
+var encodingOptions = {...};
+
+function processFileResult() {
+    // ...
+}
+
+element.successHandler = function() {
+    // ...
+};
+
+var itemSet = {
+    entities: [...]
+};
+
+callback(); // all function calls are ignored
+
+foo.callback(); // all function calls are ignored
+
+foo.data; // all property names that are not assignments are ignored
+```
+
+## When Not To Use It
+
+You can turn this rule off if you do not want to restrict the use of certain identifiers.
index f9f6de100805584c967ac7e4f493bd2c59c8520e..e9f8d9e1eb8dfff32851f3572d9f873180890427 100644 (file)
@@ -82,6 +82,7 @@ This rule has an object option:
 * `"properties": always` (default) enforces identifier length convention for property names
 * `"properties": never` ignores identifier length convention for property names
 * `"exceptions"` allows an array of specified identifier names
+* `"exceptionPatterns"` array of strings representing regular expression patterns, allows identifiers that match any of the patterns.
 
 ### min
 
@@ -217,6 +218,29 @@ const { x } = foo;
 const { a: x } = foo;
 ```
 
+### exceptionPatterns
+
+Examples of additional **correct** code for this rule with the `{ "exceptionPatterns": ["E|S", "[x-z]"] }` option:
+
+```js
+/*eslint id-length: ["error", { "exceptionPatterns": ["E|S", "[x-z]"] }]*/
+/*eslint-env es6*/
+
+var E = 5;
+function S() { return 42; }
+obj.x = document.body;
+var foo = function (x) { /* do stuff */ };
+try {
+    dangerousStuff();
+} catch (x) {
+    // ignore as many do
+}
+(y) => {return  y * y};
+var [E] = arr;
+const { y } = foo;
+const { a: z } = foo;
+```
+
 ## Related Rules
 
 * [max-len](max-len.md)
index 61af596981307f5c7f1f9bdd6a8c0143820ea7e4..ae543bd837b8aff760ee2e3ffdcbcbeb0383bd42 100644 (file)
@@ -437,7 +437,7 @@ const [
 
 ### ignorePattern
 
-By default this rule ignores comments starting with the following words: `eslint`, `jshint`, `jslint`, `istanbul`, `global`, `exported`, `jscs`. An alternative regular expression can be provided.
+By default this rule ignores comments starting with the following words: `eslint`, `jshint`, `jslint`, `istanbul`, `global`, `exported`, `jscs`. To ignore more comments in addition to the defaults, set the `ignorePattern` option to a string pattern that will be passed to the [`RegExp` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/RegExp).
 
 Examples of **correct** code for the `ignorePattern` option:
 
index c3b1449f5fc2d6408607325928aca548d187c0bd..f950a6b0105c7a89e1385142d6dcb3d4b915b362 100644 (file)
@@ -84,7 +84,7 @@ class Foo{
 Examples of **correct** code for this rule with the object option:
 
 ```js
-/* eslint lines-between-class-members: ["error", "always", { exceptAfterSingleLine: true }]*/
+/* eslint lines-between-class-members: ["error", "always", { "exceptAfterSingleLine": true }]*/
 class Foo{
   bar(){} // single line class member
   baz(){
index 0019e9836506c8378a38ca4dc84094b84ad1c0bf..de4ea0d94f2898fcee54f82eb2ae74d65eb5e19c 100644 (file)
@@ -6,6 +6,7 @@ Some people consider large files a code smell. Large files tend to do a lot of t
 
 This rule enforces a maximum number of lines per file, in order to aid in maintainability and reduce complexity.
 
+Please note that most editors show an additional empty line at the end if the file ends with a line break. This rule does not count that extra line.
 
 ## Options
 
index e9461a1ca0836a9442e8a695d09d49309b68cb28..0c38bf7d3f16f714d7460a8bbb6f3affc78bee21 100644 (file)
@@ -27,7 +27,7 @@ This rule has a string option:
 
 * `"always"` (default) enforces newlines between the operands of a ternary expression.
 * `"always-multiline"` enforces newlines between the operands of a ternary expression if the expression spans multiple lines.
-* `"never"` disallows newlines between the operands of a ternary expression (enforcing that the entire ternary expression is on one line).
+* `"never"` disallows newlines between the operands of a ternary expression.
 
 ### always
 
@@ -134,6 +134,10 @@ Examples of **correct** code for this rule with the `"never"` option:
 foo > bar ? value1 : value2;
 
 foo > bar ? (baz > qux ? value1 : value2) : value3;
+
+foo > bar ? (
+    baz > qux ? value1 : value2
+) : value3;
 ```
 
 ## When Not To Use It
index 9edf07363fe4c7d4deb4ee2f7282cdcbbc271241..eebf142374680e117c4f9e6186a8247d4da10889 100644 (file)
@@ -42,6 +42,8 @@ This rule disallows the use of `await` within loop bodies.
 Examples of **correct** code for this rule:
 
 ```js
+/*eslint no-await-in-loop: "error"*/
+
 async function foo(things) {
   const results = [];
   for (const thing of things) {
@@ -56,6 +58,8 @@ async function foo(things) {
 Examples of **incorrect** code for this rule:
 
 ```js
+/*eslint no-await-in-loop: "error"*/
+
 async function foo(things) {
   const results = [];
   for (const thing of things) {
index b4c582e895935243ac85744957da657ab5a886dc..e35b0ed5169ecbade89f469c9918ecc6b7723327 100644 (file)
@@ -22,6 +22,10 @@ foo = bar;
 function foo() {
     foo = bar;
 }
+
+var a = function hello() {
+  hello = 123;
+};
 ```
 
 Examples of **incorrect** code for this rule, unlike the corresponding rule in JSHint:
index cde77e9e49914e852ede3ab9e50a58e937081398..237b2485a72dbfff5417deae3face3198062b050 100644 (file)
@@ -87,3 +87,25 @@ var quux = (
     </div>
 )
 ```
+
+## Options
+
+### ignorePattern
+
+To make this rule ignore specific comments, set the `ignorePattern` option to a string pattern that will be passed to the [`RegExp` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/RegExp).
+
+Examples of **correct** code for the `ignorePattern` option:
+
+```js
+/*eslint no-inline-comments: ["error", { "ignorePattern": "webpackChunkName:\\s.+" }]*/
+
+import(/* webpackChunkName: "my-chunk-name" */ './locale/en');
+```
+
+Examples of **incorrect** code for the `ignorePattern` option:
+
+```js
+/*eslint no-inline-comments: ["error", { "ignorePattern": "something" }] */
+
+var foo = 4; // other thing
+```
index e1fcc4e09d7cf293b0e58061b731489c50d2f6df..62d065981e6a87675798fac1fe2b35d196a4d6c1 100644 (file)
@@ -16,6 +16,7 @@ const x = 5123000000000000000000000000001
 const x = 1230000000000000000000000.0
 const x = .1230000000000000000000000
 const x = 0X20000000000001
+const x = 0X2_000000000_0001;
 ```
 
 Examples of **correct** code for this rule:
@@ -29,4 +30,5 @@ const x = 123e34
 const x = 12300000000000000000000000
 const x = 0x1FFFFFFFFFFFFF
 const x = 9007199254740991
+const x = 9007_1992547409_91
 ```
index ee6d3de7ee7d4119e1aa29a114890c189982fe65..dfe21d83c1be1e6148b36ff89164f9c3238780d1 100644 (file)
@@ -128,6 +128,27 @@ a = data[4294967295]; // above the max array index
 a = data[1e500]; // same as data["Infinity"]
 ```
 
+### ignoreDefaultValues
+
+A boolean to specify if numbers used in default value assignments are considered okay. `false` by default.
+
+Examples of **correct** code for the `{ "ignoreDefaultValues": true }` option:
+
+```js
+/*eslint no-magic-numbers: ["error", { "ignoreDefaultValues": true }]*/
+
+const { tax = 0.25 } = accountancy;
+
+function mapParallel(concurrency = 3) { /***/ }
+```
+
+```js
+/*eslint no-magic-numbers: ["error", { "ignoreDefaultValues": true }]*/
+
+let head;
+[head = 100] = []
+```
+
 ### enforceConst
 
 A boolean to specify if we should check for the const keyword in variable declaration of numbers. `false` by default.
index 87b2425e0106d07200203d642a8439dc86ccf64f..aff6c2e6f1b7545fe5cf15c220fbc1ec509f0c82 100644 (file)
@@ -10,9 +10,9 @@ This rule aims to reduce the scrolling required when reading through your code.
 
 This rule has an object option:
 
-* `"max"` (default: `2`) enforces a maximum number of consecutive empty lines.
-* `"maxEOF"` enforces a maximum number of consecutive empty lines at the end of files.
-* `"maxBOF"` enforces a maximum number of consecutive empty lines at the beginning of files.
+-   `"max"` (default: `2`) enforces a maximum number of consecutive empty lines.
+-   `"maxEOF"` enforces a maximum number of consecutive empty lines at the end of files.
+-   `"maxBOF"` enforces a maximum number of consecutive empty lines at the beginning of files.
 
 ### max
 
@@ -41,10 +41,10 @@ var bar = 3;
 
 ### maxEOF
 
-Examples of **incorrect** code for this rule with the `{ max: 2, maxEOF: 1 }` options:
+Examples of **incorrect** code for this rule with the `{ max: 2, maxEOF: 0 }` options:
 
 ```js
-/*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 1 }]*/
+/*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 0 }]*/
 
 var foo = 5;
 
@@ -54,16 +54,42 @@ var bar = 3;
 
 ```
 
-Examples of **correct** code for this rule with the `{ max: 2, maxEOF: 1 }` options:
+Examples of **correct** code for this rule with the `{ max: 2, maxEOF: 0 }` options:
 
 ```js
-/*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 1 }]*/
+/*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 0 }]*/
 
 var foo = 5;
 
 
 var bar = 3;
+```
+
+**Note**: Although this ensures zero empty lines at the EOF, most editors will still show one empty line at the end if the file ends with a line break, as illustrated below. There is no empty line at the end of a file after the last `\n`, although editors may show an additional line. A true additional line would be represented by `\n\n`.
+
+**Incorrect**:
+
+```
+1    /*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 0 }]*/⏎
+2    ⏎
+3    var foo = 5;⏎
+4    ⏎
+5    ⏎
+6    var bar = 3;⏎
+7    ⏎
+8
+```
 
+**Correct**:
+
+```
+1    /*eslint no-multiple-empty-lines: ["error", { "max": 2, "maxEOF": 0 }]*/⏎
+2    ⏎
+3    var foo = 5;⏎
+4    ⏎
+5    ⏎
+6    var bar = 3;⏎
+7
 ```
 
 ### maxBOF
diff --git a/eslint/docs/rules/no-promise-executor-return.md b/eslint/docs/rules/no-promise-executor-return.md
new file mode 100644 (file)
index 0000000..1adfe37
--- /dev/null
@@ -0,0 +1,96 @@
+# Disallow returning values from Promise executor functions (no-promise-executor-return)
+
+The `new Promise` constructor accepts a single argument, called an *executor*.
+
+```js
+const myPromise = new Promise(function executor(resolve, reject) {
+    readFile('foo.txt', function(err, result) {
+        if (err) {
+            reject(err);
+        } else {
+            resolve(result);
+        }
+    });
+});
+```
+
+The executor function usually initiates some asynchronous operation. Once it is finished, the executor should call `resolve` with the result, or `reject` if an error occurred.
+
+The return value of the executor is ignored. Returning a value from an executor function is a possible error because the returned value cannot be used and it doesn't affect the promise in any way.
+
+## Rule Details
+
+This rule disallows returning values from Promise executor functions.
+
+Only `return` without a value is allowed, as it's a control flow statement.
+
+Examples of **incorrect** code for this rule:
+
+```js
+/*eslint no-promise-executor-return: "error"*/
+
+new Promise((resolve, reject) => {
+    if (someCondition) {
+        return defaultResult;
+    }
+    getSomething((err, result) => {
+        if (err) {
+            reject(err);
+        } else {
+            resolve(result);
+        }
+    });
+});
+
+new Promise((resolve, reject) => getSomething((err, data) => {
+    if (err) {
+        reject(err);
+    } else {
+        resolve(data);
+    }
+}));
+
+new Promise(() => {
+    return 1;
+});
+```
+
+Examples of **correct** code for this rule:
+
+```js
+/*eslint no-promise-executor-return: "error"*/
+
+new Promise((resolve, reject) => {
+    if (someCondition) {
+        resolve(defaultResult);
+        return;
+    }
+    getSomething((err, result) => {
+        if (err) {
+            reject(err);
+        } else {
+            resolve(result);
+        }
+    });
+});
+
+new Promise((resolve, reject) => {
+    getSomething((err, data) => {
+        if (err) {
+            reject(err);
+        } else {
+            resolve(data);
+        }
+    });
+});
+
+Promise.resolve(1);
+```
+
+## Further Reading
+
+* [MDN Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
+
+## Related Rules
+
+* [no-async-promise-executor](no-async-promise-executor.md)
index b1c60f092ae4b8694750f215c4e83ffa6ebc3c1c..e3f7be259d7e8a94d4a77eb2b1ddfb9c3000f1df 100644 (file)
@@ -11,6 +11,8 @@ This rule aims to prevent a likely common performance hazard due to a lack of un
 Examples of **incorrect** code for this rule:
 
 ```js
+/*eslint no-return-await: "error"*/
+
 async function foo() {
     return await bar();
 }
@@ -19,6 +21,8 @@ async function foo() {
 Examples of **correct** code for this rule:
 
 ```js
+/*eslint no-return-await: "error"*/
+
 async function foo() {
     return bar();
 }
@@ -28,11 +32,13 @@ async function foo() {
     return;
 }
 
+// This is essentially the same as `return await bar();`, but the rule checks only `await` in `return` statements
 async function foo() {
     const x = await bar();
     return x;
 }
 
+// In this example the `await` is necessary to be able to catch errors thrown from `bar()`
 async function foo() {
     try {
         return await bar();
@@ -40,8 +46,6 @@ async function foo() {
 }
 ```
 
-In the last example the `await` is necessary to be able to catch errors thrown from `bar()`.
-
 ## When Not To Use It
 
 There are a few reasons you might want to turn this rule off:
index 475959dacb1696c034e61bf4c37ee6122a8ad882..5c71eb413275041e3394058680c81fd9b53813c3 100644 (file)
@@ -10,6 +10,8 @@ Examples of **incorrect** code for this rule:
 /*eslint no-script-url: "error"*/
 
 location.href = "javascript:void(0)";
+
+location.href = `javascript:void(0)`;
 ```
 
 ## Compatibility
index ebe7fd7b6d0db0e1caf2d5b9b9a08f3fb7711469..ded42815d2a34781e060fa737d9f8c92c2a475fe 100644 (file)
@@ -33,17 +33,21 @@ var _ = require('underscore');
 var obj = _.contains(items, item);
 obj.__proto__ = {};
 var file = __filename;
+function foo(_bar) {};
+const foo = { onClick(_bar) {} };
+const foo = (_bar) => {};
 ```
 
 ## Options
 
 This rule has an object option:
 
-* `"allow"` allows specified identifiers to have dangling underscores
-* `"allowAfterThis": false` (default) disallows dangling underscores in members of the `this` object
-* `"allowAfterSuper": false` (default) disallows dangling underscores in members of the `super` object
-* `"allowAfterThisConstructor": false` (default) disallows dangling underscores in members of the `this.constructor` object
-* `"enforceInMethodNames": false` (default) allows dangling underscores in method names
+-  `"allow"` allows specified identifiers to have dangling underscores
+-  `"allowAfterThis": false` (default) disallows dangling underscores in members of the `this` object
+-  `"allowAfterSuper": false` (default) disallows dangling underscores in members of the `super` object
+-  `"allowAfterThisConstructor": false` (default) disallows dangling underscores in members of the `this.constructor` object
+-  `"enforceInMethodNames": false` (default) allows dangling underscores in method names
+-  `"allowFunctionParams": true` (default) allows dangling underscores in function parameter names
 
 ### allow
 
@@ -113,6 +117,26 @@ const o = {
 };
 ```
 
+### allowFunctionParams
+
+Examples of **incorrect** code for this rule with the `{ "allowFunctionParams": false }` option:
+
+```js
+/*eslint no-underscore-dangle: ["error", { "allowFunctionParams": false }]*/
+
+function foo (_bar) {}
+function foo (_bar = 0) {}
+function foo (..._bar) {}
+
+const foo = function onClick (_bar) {}
+const foo = function onClick (_bar = 0) {}
+const foo = function onClick (..._bar) {}
+
+const foo = (_bar) => {};
+const foo = (_bar = 0) => {};
+const foo = (..._bar) => {};
+```
+
 ## When Not To Use It
 
 If you want to allow dangling underscores in identifiers, then you can safely turn this rule off.
index 688c350d2daaf8ae7b35e9d487e1aaf94e76715c..7d9bceeedef52d382f88ea099a525e51f93910d1 100644 (file)
@@ -29,6 +29,10 @@ If a reference is inside of a dynamic expression (e.g. `CallExpression`,
 Examples of **incorrect** code for this rule:
 
 ```js
+/*eslint no-unmodified-loop-condition: "error"*/
+
+var node = something;
+
 while (node) {
     doSomething(node);
 }
@@ -46,6 +50,8 @@ while (node !== root) {
 Examples of **correct** code for this rule:
 
 ```js
+/*eslint no-unmodified-loop-condition: "error"*/
+
 while (node) {
     doSomething(node);
     node = node.parent;
diff --git a/eslint/docs/rules/no-unreachable-loop.md b/eslint/docs/rules/no-unreachable-loop.md
new file mode 100644 (file)
index 0000000..c58b97f
--- /dev/null
@@ -0,0 +1,190 @@
+# Disallow loops with a body that allows only one iteration (no-unreachable-loop)
+
+A loop that can never reach the second iteration is a possible error in the code.
+
+```js
+for (let i = 0; i < arr.length; i++) {
+    if (arr[i].name === myName) {
+        doSomething(arr[i]);
+        // break was supposed to be here
+    }
+    break;
+}
+```
+
+In rare cases where only one iteration (or at most one iteration) is intended behavior, the code should be refactored to use `if` conditionals instead of `while`, `do-while` and `for` loops. It's considered a best practice to avoid using loop constructs for such cases.
+
+## Rule Details
+
+This rule aims to detect and disallow loops that can have at most one iteration, by performing static code path analysis on loop bodies.
+
+In particular, this rule will disallow a loop with a body that exits the loop in all code paths. If all code paths in the loop's body will end with either a `break`, `return` or a `throw` statement, the second iteration of such loop is certainly unreachable, regardless of the loop's condition.
+
+This rule checks `while`, `do-while`, `for`, `for-in` and `for-of` loops. You can optionally disable checks for each of these constructs.
+
+Examples of **incorrect** code for this rule:
+
+```js
+/*eslint no-unreachable-loop: "error"*/
+
+while (foo) {
+    doSomething(foo);
+    foo = foo.parent;
+    break;
+}
+
+function verifyList(head) {
+    let item = head;
+    do {
+        if (verify(item)) {
+            return true;
+        } else {
+            return false;
+        }
+    } while (item);
+}
+
+function findSomething(arr) {
+    for (var i = 0; i < arr.length; i++) {
+        if (isSomething(arr[i])) {
+            return arr[i];
+        } else {
+            throw new Error("Doesn't exist.");
+        }
+    }
+}
+
+for (key in obj) {
+    if (key.startsWith("_")) {
+        break;
+    }
+    firstKey = key;
+    firstValue = obj[key];
+    break;
+}
+
+for (foo of bar) {
+    if (foo.id === id) {
+        doSomething(foo);
+    }
+    break;
+}
+```
+
+Examples of **correct** code for this rule:
+
+```js
+/*eslint no-unreachable-loop: "error"*/
+
+while (foo) {
+    doSomething(foo);
+    foo = foo.parent;
+}
+
+function verifyList(head) {
+    let item = head;
+    do {
+        if (verify(item)) {
+            item = item.next;
+        } else {
+            return false;
+        }
+    } while (item);
+
+    return true;
+}
+
+function findSomething(arr) {
+    for (var i = 0; i < arr.length; i++) {
+        if (isSomething(arr[i])) {
+            return arr[i];
+        }
+    }
+    throw new Error("Doesn't exist.");
+}
+
+for (key in obj) {
+    if (key.startsWith("_")) {
+        continue;
+    }
+    firstKey = key;
+    firstValue = obj[key];
+    break;
+}
+
+for (foo of bar) {
+    if (foo.id === id) {
+        doSomething(foo);
+        break;
+    }
+}
+```
+
+Please note that this rule is not designed to check loop conditions, and will not warn in cases such as the following examples.
+
+Examples of additional **correct** code for this rule:
+
+```js
+/*eslint no-unreachable-loop: "error"*/
+
+do {
+    doSomething();
+} while (false)
+
+for (let i = 0; i < 1; i++) {
+    doSomething(i);
+}
+
+for (const a of [1]) {
+    doSomething(a);
+}
+```
+
+## Options
+
+This rule has an object option, with one option:
+
+* `"ignore"` - an optional array of loop types that will be ignored by this rule.
+
+## ignore
+
+You can specify up to 5 different elements in the `"ignore"` array:
+
+* `"WhileStatement"` - to ignore all `while` loops.
+* `"DoWhileStatement"` - to ignore all `do-while` loops.
+* `"ForStatement"` - to ignore all `for` loops (does not apply to `for-in` and `for-of` loops).
+* `"ForInStatement"` - to ignore all `for-in` loops.
+* `"ForOfStatement"` - to ignore all `for-of` loops.
+
+Examples of **correct** code for this rule with the `"ignore"` option:
+
+```js
+/*eslint no-unreachable-loop: ["error", { "ignore": ["ForInStatement", "ForOfStatement"] }]*/
+
+for (var key in obj) {
+  hasEnumerableProperties = true;
+  break;
+}
+
+for (const a of b) break;
+```
+
+## Known Limitations
+
+Static code path analysis, in general, does not evaluate conditions. Due to this fact, this rule might miss reporting cases such as the following:
+
+```js
+for (let i = 0; i < 10; i++) {
+    doSomething(i);
+    if (true) {
+        break;
+    }
+}
+```
+
+## Related Rules
+
+* [no-unreachable](no-unreachable.md)
+* [no-constant-condition](no-constant-condition.md)
+* [no-unmodified-loop-condition](no-unmodified-loop-condition.md)
+* [for-direction](for-direction.md)
index eabc03eacf4c1a1488953cd091619f388b57f3e3..302c81c33b5550465c8c006a6ba23fc81208c81e 100644 (file)
@@ -335,19 +335,32 @@ let d = {
     foo: 1, bar: 2};
 let e = {foo: function() {
     dosomething();
-}};
+    }
+};
+let f = {
+    foo: function() {
+    dosomething();}};
 
-let {f
+let {g
 } = obj;
 let {
-    g} = obj;
-let {h, i
+    h} = obj;
+let {i, j
 } = obj;
+let {k, l
+} = obj;
+let {
+    m, n} = obj;
 let {
-    j, k} = obj;
-let {l = function() {
+    o, p} = obj;
+let {q = function() {
     dosomething();
-}} = obj;
+    }
+} = obj;
+let {
+    r = function() {
+        dosomething();
+    }} = obj;
 ```
 
 Examples of **correct** code for this rule with the default `{ "consistent": true }` option:
@@ -356,27 +369,35 @@ Examples of **correct** code for this rule with the default `{ "consistent": tru
 /*eslint object-curly-newline: ["error", { "consistent": true }]*/
 /*eslint-env es6*/
 
-let a = {};
-let b = {foo: 1};
-let c = {
+
+let empty1 = {};
+let empty2 = {
+};
+let a = {foo: 1};
+let b = {
     foo: 1
 };
-let d = {
+let c = {
     foo: 1, bar: 2
 };
-let e = {
+let d = {
     foo: 1,
     bar: 2
 };
-let f = {foo: function() {dosomething();}};
-let g = {
+let e = {foo: function() {dosomething();}};
+let f = {
     foo: function() {
         dosomething();
     }
 };
 
 let {} = obj;
-let {h} = obj;
+let {
+} = obj;
+let {g} = obj;
+let {
+    h
+} = obj;
 let {i, j} = obj;
 let {
     k, l
index 570f57c655c2e29b0b47d7c11df2922cc256d438..b926462b3094a7f2f300736f2a8ba7fcb81761ff 100644 (file)
@@ -10,6 +10,7 @@ JavaScript provides shorthand operators that combine variable assignment and som
  x *= y    | x = x * y
  x /= y    | x = x / y
  x %= y    | x = x % y
+ x **= y   | x = x ** y
  x <<= y   | x = x << y
  x >>= y   | x = x >> y
  x >>>= y  | x = x >>> y
@@ -22,6 +23,8 @@ JavaScript provides shorthand operators that combine variable assignment and som
 
 This rule requires or disallows assignment operator shorthand where possible.
 
+The rule applies to the operators listed in the above table. It does not report the logical assignment operators `&&=`, `||=`, and `??=` because their short-circuiting behavior is different from the other assignment operators.
+
 ## Options
 
 This rule has a single string option:
index 3b1986e0fffaff7276930e1f8bd46353dc38ee2b..159bd528ad597201771b98f58e0c1b5630df36ce 100644 (file)
@@ -22,7 +22,7 @@ This rule enforces a consistent linebreak style for operators.
 
 ## Options
 
-This rule has one option, which can be a string option or an object option.
+This rule has two options, a string option and an object option.
 
 String option:
 
index 1f2c4ab89508b5fb838ae180c97eaae3209e197b..2f2f67ee07cece632f315204626fbe2aee083010 100644 (file)
@@ -48,14 +48,14 @@ You can supply any number of configurations. If a statement pair matches multipl
     - `"block"` is lonely blocks.
     - `"block-like"` is block like statements. This matches statements that the last token is the closing brace of blocks; e.g. `{ }`, `if (a) { }`, and `while (a) { }`. Also matches immediately invoked function expression statements.
     - `"break"` is `break` statements.
-    - `"case"` is `case` labels.
+    - `"case"` is `case` clauses in `switch` statements.
     - `"cjs-export"` is `export` statements of CommonJS; e.g. `module.exports = 0`, `module.exports.foo = 1`, and `exports.foo = 2`. This is a special case of assignment.
     - `"cjs-import"` is `import` statements of CommonJS; e.g. `const foo = require("foo")`. This is a special case of variable declarations.
     - `"class"` is `class` declarations.
     - `"const"` is `const` variable declarations, both single-line and multiline.
     - `"continue"` is `continue` statements.
     - `"debugger"` is `debugger` statements.
-    - `"default"` is `default` labels.
+    - `"default"` is `default` clauses in `switch` statements.
     - `"directive"` is directive prologues. This matches directives; e.g. `"use strict"`.
     - `"do"` is `do-while` statements. This matches all statements that the first token is `do` keyword.
     - `"empty"` is empty statements.
@@ -212,6 +212,55 @@ Examples of **correct** code for the `[{ blankLine: "always", prev: "directive",
 foo();
 ```
 
+----
+
+This configuration would require blank lines between clauses in `switch` statements.
+
+Examples of **incorrect** code for the `[{ blankLine: "always", prev: ["case", "default"], next: "*" }]` configuration:
+
+```js
+/*eslint padding-line-between-statements: [
+    "error",
+    { blankLine: "always", prev: ["case", "default"], next: "*" }
+]*/
+
+switch (foo) {
+    case 1:
+        bar();
+        break;
+    case 2:
+    case 3:
+        baz();
+        break;
+    default:
+        quux();
+}
+```
+
+Examples of **correct** code for the `[{ blankLine: "always", prev: ["case", "default"], next: "*" }]` configuration:
+
+```js
+/*eslint padding-line-between-statements: [
+    "error",
+    { blankLine: "always", prev: ["case", "default"], next: "*" }
+]*/
+
+switch (foo) {
+    case 1:
+        bar();
+        break;
+
+    case 2:
+
+    case 3:
+        baz();
+        break;
+
+    default:
+        quux();
+}
+```
+
 ## Compatibility
 
 - **JSCS:** [requirePaddingNewLineAfterVariableDeclaration]
index 00b55774b21a8449803e63d86bd17d23510bdd37..7f18559ed501ce8e61baa50ecb4f0df284fd8867 100644 (file)
@@ -21,6 +21,8 @@ The rule has a second object with a single key, `enforceForRenamedProperties`, w
 - Accessing an object property whose key is an integer will fall under the category `array` destructuring.
 - Accessing an array element through a computed index will fall under the category `object` destructuring.
 
+The `--fix` option on the command line fixes only problems reported in variable declarations, and among them only those that fall under the category `object` destructuring. Furthermore, the name of the declared variable has to be the same as the name used for non-computed member access in the initializer. For example, `var foo = object.foo` can be automatically fixed by this rule. Problems that involve computed member access (e.g., `var foo = object[foo]`) or renamed properties (e.g., `var foo = object.bar`) are not automatically fixed.
+
 Examples of **incorrect** code for this rule:
 
 ```javascript
index 95a646068f81437acd4b9b9c7d71d4dadd3a5775..50d721b79ad6d521d789dcbce30e7facafe7916b 100644 (file)
@@ -9,6 +9,7 @@ Introduced in ES2018, object spread is a declarative alternative which may perfo
 Examples of **incorrect** code for this rule:
 
 ```js
+/*eslint prefer-object-spread: "error"*/
 
 Object.assign({}, foo)
 
@@ -31,6 +32,7 @@ Object.assign({ foo: bar });
 Examples of **correct** code for this rule:
 
 ```js
+/*eslint prefer-object-spread: "error"*/
 
 Object.assign(...foo);
 
index fea589d3412bac7ef632ccb55b1cc073239d9b0c..2ba8cacc08d4e86adb70b52bb37f09b041334b27 100644 (file)
@@ -88,6 +88,38 @@ RegExp(`${prefix}abc`);
 new RegExp(String.raw`^\d\. ${suffix}`);
 ```
 
+## Options
+
+This rule has an object option:
+
+* `disallowRedundantWrapping` set to `true` additionally checks for unnecessarily wrapped regex literals (Default `false`).
+
+### `disallowRedundantWrapping`
+
+By default, this rule doesn’t check when a regex literal is unnecessarily wrapped in a `RegExp` constructor call. When the option `disallowRedundantWrapping` is set to `true`, the rule will also disallow such unnecessary patterns.
+
+Examples of `incorrect` code for `{ "disallowRedundantWrapping": true }`
+
+```js
+/*eslint prefer-regex-literals: ["error", {"disallowRedundantWrapping": true}]*/
+
+new RegExp(/abc/);
+
+new RegExp(/abc/, 'u');
+```
+
+Examples of `correct` code for `{ "disallowRedundantWrapping": true }`
+
+```js
+/*eslint prefer-regex-literals: ["error", {"disallowRedundantWrapping": true}]*/
+
+/abc/;
+
+/abc/u;
+
+new RegExp(/abc/, flags);
+```
+
 ## Further Reading
 
 * [MDN: Regular Expressions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions)
index f6fd04f0683d4140262fb5c63eb8a531750f5a31..637c9fcf3f383b9641806436abf00e174b6cddc5 100644 (file)
@@ -41,6 +41,7 @@ This rule accepts an object with its properties as
     * `all` = import all members provided by exported bindings.
     * `multiple` = import multiple members.
     * `single` = import single member.
+* `allowSeparatedGroups` (default: `false`)
 
 Default option settings are:
 
@@ -50,7 +51,8 @@ Default option settings are:
         "ignoreCase": false,
         "ignoreDeclarationSort": false,
         "ignoreMemberSort": false,
-        "memberSyntaxSortOrder": ["none", "all", "multiple", "single"]
+        "memberSyntaxSortOrder": ["none", "all", "multiple", "single"],
+        "allowSeparatedGroups": false
     }]
 }
 ```
@@ -226,6 +228,53 @@ import {a, b} from 'foo.js';
 
 Default is `["none", "all", "multiple", "single"]`.
 
+### `allowSeparatedGroups`
+
+When `true` the rule checks the sorting of import declaration statements only for those that appear on consecutive lines.
+
+In other words, a blank line or a comment line or line with any other statement after an import declaration statement will reset the sorting of import declaration statements.
+
+Examples of **incorrect** code for this rule with the `{ "allowSeparatedGroups": true }` option:
+
+```js
+/*eslint sort-imports: ["error", { "allowSeparatedGroups": true }]*/
+
+import b from 'foo.js';
+import c from 'bar.js';
+import a from 'baz.js';
+```
+
+Examples of **correct** code for this rule with the `{ "allowSeparatedGroups": true }` option:
+
+```js
+/*eslint sort-imports: ["error", { "allowSeparatedGroups": true }]*/
+
+import b from 'foo.js';
+import c from 'bar.js';
+
+import a from 'baz.js';
+```
+
+```js
+/*eslint sort-imports: ["error", { "allowSeparatedGroups": true }]*/
+
+import b from 'foo.js';
+import c from 'bar.js';
+// comment
+import a from 'baz.js';
+```
+
+```js
+/*eslint sort-imports: ["error", { "allowSeparatedGroups": true }]*/
+
+import b from 'foo.js';
+import c from 'bar.js';
+quux();
+import a from 'baz.js';
+```
+
+Default is `false`.
+
 ## When Not To Use It
 
 This rule is a formatting preference and not following it won't negatively affect the quality of your code. If alphabetizing imports isn't a part of your coding standards, then you can leave this rule disabled.
index d04bd4a4561c50a34ce0870570e69dad5be5737e..676a921bf635f57c5fb827d058cfbc6a5c7acb46 100644 (file)
@@ -6,6 +6,8 @@ Some style guides require or disallow spaces before or after unary operators. Th
 
 This rule enforces consistency regarding the spaces after `words` unary operators and after/before `nonwords` unary operators.
 
+For `words` operators, this rule only applies when a space is not syntactically required. For instance, `delete obj.foo` requires the space and will not be considered by this rule. The equivalent `delete(obj.foo)` has an optional space (`delete (obj.foo)`), therefore this rule will apply to it.
+
 Examples of unary `words` operators:
 
 ```js
@@ -103,14 +105,17 @@ Examples of **correct** code for this rule with the `{"words": true, "nonwords":
 ```js
 /*eslint space-unary-ops: "error"*/
 
-// Word unary operator "delete" is followed by a whitespace.
-delete foo.bar;
+// Word unary operator "typeof" is followed by a whitespace.
+typeof !foo;
+
+// Word unary operator "void" is followed by a whitespace.
+void {foo:0};
 
 // Word unary operator "new" is followed by a whitespace.
-new Foo;
+new [foo][0];
 
-// Word unary operator "void" is followed by a whitespace.
-void 0;
+// Word unary operator "delete" is followed by a whitespace.
+delete (foo.bar);
 
 // Unary operator "++" is not followed by whitespace.
 ++foo;
index b1884a874a9cd66b369a19562fab192828e4967d..825e3bfae130cc267c1c6749f02e1bcf8bae0bbd 100644 (file)
@@ -37,6 +37,8 @@ typeof bar === typeof qux
 Examples of **incorrect** code with the `{ "requireStringLiterals": true }` option:
 
 ```js
+/*eslint valid-typeof: ["error", { "requireStringLiterals": true }]*/
+
 typeof foo === undefined
 typeof bar == Object
 typeof baz === "strnig"
@@ -48,6 +50,8 @@ typeof foo == 5
 Examples of **correct** code with the `{ "requireStringLiterals": true }` option:
 
 ```js
+/*eslint valid-typeof: ["error", { "requireStringLiterals": true }]*/
+
 typeof foo === "undefined"
 typeof bar == "object"
 typeof baz === "string"
index 7f880198bf41995192d478fbe147c06acba30894..96b8d6ef174a8ff5e39a0ee5efbe48a6e3dae520 100644 (file)
@@ -13,6 +13,27 @@ There are several pieces of information that can be configured:
 
 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.
@@ -24,7 +45,7 @@ For ES6 syntax, use `{ "parserOptions": { "ecmaVersion": 6 } }`; for new ES6 glo
 { "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 or 11 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) or 2020 (same as 11) to use the year-based naming.
+* `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
@@ -73,7 +94,7 @@ To indicate the npm module to use as your parser, specify it using the `parser`
 The following parsers are compatible with ESLint:
 
 * [Esprima](https://www.npmjs.com/package/esprima)
-* [Babel-ESLint](https://www.npmjs.com/package/babel-eslint) - A wrapper around the [Babel](https://babeljs.io) parser that makes it compatible with ESLint.
+* [@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.
@@ -91,7 +112,7 @@ To specify processors in a configuration file, use the `processor` key with the
 }
 ```
 
-To specify processors for a specific kind 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.
+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
 {
@@ -138,6 +159,7 @@ An environment defines global variables that are predefined. The available envir
 * `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.
@@ -1070,18 +1092,36 @@ Of particular note is that like `.gitignore` files, all paths used as patterns f
 
 Please see `.gitignore`'s specification for further examples of valid syntax.
 
-In addition to any patterns in a `.eslintignore` file, ESLint ignores files in `/**/node_modules/*` by default. It can still be added using `!`.
+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:
 
-For example, placing the following `.eslintignore` file in the current working directory will not ignore `node_modules/*` and ignore anything in the `build/` directory except `build/index.js`:
+* `node_modules/` is ignored.
+* Dotfiles (except for `.eslintrc.*`) as well as Dotfolders and their contents are ignored.
 
-```text
-# node_modules/* is ignored by default, but can be added using !
-!node_modules/*
+There are also some exceptions to these rules:
 
-# Ignore built files except build/index.js
-build/*
-!build/index.js
-```
+* 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
 
@@ -1127,13 +1167,28 @@ You'll see this warning:
 
 ```text
 foo.js
-  0:0  warning  File ignored because of your .eslintignore file. Use --no-ignore to override.
+  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).
index 8438d50af54a2697cf3f588b8c1c969147e5fe4a..93fef31e061120c17321e329d40f4a8134f08a4f 100644 (file)
@@ -20,16 +20,26 @@ npm install eslint --save-dev
 yarn add eslint --dev
 ```
 
-You should then set up a configuration file:
+You should then set up a configuration file, and the easiest way to do that is to use the `--init` flag:
 
 ```
 $ npx eslint --init
+
+# or
+
+$ yarn run eslint --init
 ```
 
+**Note:** `--init` assumes you have a `package.json` file already. If you don't, make sure to run `npm init` or `yarn init` beforehand.
+
 After that, you can run ESLint on any file or directory like this:
 
 ```
 $ npx eslint yourfile.js
+
+# or
+
+$ yarn run eslint yourfile.js
 ```
 
 It is also possible to install ESLint globally rather than locally (using `npm install eslint --global`). However, this is not recommended, and any plugins or shareable configs that you use must be installed locally in either case.
index de1d3781b8d7cb3ae7c5109b2449f73722153997..b62ef053c09cad39f65882e061aa6d81747e8bf0 100644 (file)
@@ -30,7 +30,7 @@ The lists below are ordered roughly by the number of users each change is expect
 
 ---
 
-## <a name="eslint-recommended-changes"/> `eslint:recommended` changes
+## <a name="eslint-recommended-changes"></a> `eslint:recommended` changes
 
 Two new rules have been added to the [`eslint:recommended`](https://eslint.org/docs/user-guide/configuring#using-eslintrecommended) config:
 
@@ -50,7 +50,7 @@ Two new rules have been added to the [`eslint:recommended`](https://eslint.org/d
 }
 ```
 
-## <a name="indent-rewrite"/> The `indent` rule is more strict
+## <a name="indent-rewrite"></a> The `indent` rule is more strict
 
 Previously, the [`indent`](/docs/rules/indent) rule was fairly lenient about checking indentation; there were many code patterns where indentation was not validated by the rule. This caused confusion for users, because they were accidentally writing code with incorrect indentation, and they expected ESLint to catch the issues.
 
@@ -69,25 +69,25 @@ To make the upgrade process easier, we've introduced the [`indent-legacy`](/docs
 }
 ```
 
-## <a name="config-validation"/> Unrecognized properties in config files now cause a fatal error
+## <a name="config-validation"></a> Unrecognized properties in config files now cause a fatal error
 
 When creating a config, users sometimes make typos or misunderstand how the config is supposed to be structured. Previously, ESLint did not validate the properties of a config file, so a typo in a config could be very tedious to debug. Starting in 4.0.0, ESLint will raise an error if a property in a config file is unrecognized or has the wrong type.
 
 **To address:** If you see a config validation error after upgrading, verify that your config doesn't contain any typos. If you are using an unrecognized property, you should be able to remove it from your config to restore the previous behavior.
 
-## <a name="eslintignore-patterns"/> .eslintignore patterns are now resolved from the location of the file
+## <a name="eslintignore-patterns"></a> .eslintignore patterns are now resolved from the location of the file
 
 Due to a bug, glob patterns in an `.eslintignore` file were previously resolved from the current working directory of the process, rather than the location of the `.eslintignore` file. Starting in 4.0, patterns in an `.eslintignore` file will be resolved from the `.eslintignore` file's location.
 
 **To address:** If you use an `.eslintignore` file and you frequently run ESLint from somewhere other than the project root, it's possible that the patterns will be matched differently. You should update the patterns in the `.eslintignore` file to ensure they are relative to the file, not to the working directory.
 
-## <a name="padded-blocks-defaults"/> The `padded-blocks` rule is more strict by default
+## <a name="padded-blocks-defaults"></a> The `padded-blocks` rule is more strict by default
 
 By default, the [`padded-blocks`](/docs/rules/padded-blocks) rule will now enforce padding in class bodies and switch statements. Previously, the rule would ignore these cases unless the user opted into enforcing them.
 
 **To address:** If this change results in more linting errors in your codebase, you should fix them or reconfigure the rule.
 
-## <a name="space-before-function-paren-defaults"/> The `space-before-function-paren` rule is more strict by default
+## <a name="space-before-function-paren-defaults"></a> The `space-before-function-paren` rule is more strict by default
 
 By default, the [`space-before-function-paren`](/docs/rules/space-before-function-paren) rule will now enforce spacing for async arrow functions. Previously, the rule would ignore these cases unless the user opted into enforcing them.
 
@@ -105,7 +105,7 @@ By default, the [`space-before-function-paren`](/docs/rules/space-before-functio
 }
 ```
 
-## <a name="no-multi-spaces-eol-comments"/> The `no-multi-spaces` rule is more strict by default
+## <a name="no-multi-spaces-eol-comments"></a> The `no-multi-spaces` rule is more strict by default
 
 By default, the [`no-multi-spaces`](/docs/rules/no-multi-spaces) rule will now disallow multiple spaces before comments at the end of a line. Previously, the rule did not check this case.
 
@@ -119,7 +119,7 @@ By default, the [`no-multi-spaces`](/docs/rules/no-multi-spaces) rule will now d
 }
 ```
 
-## <a name="scoped-plugin-resolution"/> References to scoped plugins in config files are now required to include the scope
+## <a name="scoped-plugin-resolution"></a> References to scoped plugins in config files are now required to include the scope
 
 In 3.x, there was a bug where references to scoped NPM packages as plugins in config files could omit the scope. For example, in 3.x the following config was legal:
 
@@ -153,13 +153,13 @@ To avoid this ambiguity, in 4.0 references to scoped plugins must include the sc
 
 ---
 
-## <a name="rule-tester-validation"/> `RuleTester` now validates properties of test cases
+## <a name="rule-tester-validation"></a> `RuleTester` now validates properties of test cases
 
 Starting in 4.0, the `RuleTester` utility will validate properties of test case objects, and an error will be thrown if an unknown property is encountered. This change was added because we found that it was relatively common for developers to make typos in rule tests, often invalidating the assertions that the test cases were trying to make.
 
 **To address:** If your tests for custom rules have extra properties, you should remove those properties.
 
-## <a name="comment-attachment"/> AST Nodes no longer have comment properties
+## <a name="comment-attachment"></a> AST Nodes no longer have comment properties
 
 Prior to 4.0, ESLint required parsers to implement comment attachment, a process where AST nodes would gain additional properties corresponding to their leading and trailing comments in the source file. This made it difficult for users to develop custom parsers, because they would have to replicate the confusing comment attachment semantics required by ESLint.
 
@@ -177,7 +177,7 @@ Finally, please note that the following `SourceCode` methods have been deprecate
 * `getTokenOrCommentBefore()` - replaced by `getTokenBefore()` with the `{ includeComments: true }` option
 * `getTokenOrCommentAfter()` - replaced by `getTokenAfter()` with the `{ includeComments: true }` option
 
-## <a name="event-comments"/> `LineComment` and `BlockComment` events will no longer be emitted during AST traversal
+## <a name="event-comments"></a> `LineComment` and `BlockComment` events will no longer be emitted during AST traversal
 
 Starting in 4.0, `LineComment` and `BlockComments` events will not be emitted during AST traversal. There are two reasons for this:
 
@@ -191,7 +191,7 @@ sourceCode.getAllComments().filter(comment => comment.type === "Line");
 sourceCode.getAllComments().filter(comment => comment.type === "Block");
 ```
 
-## <a name="shebangs"/> Shebangs are now returned from comment APIs
+## <a name="shebangs"></a> Shebangs are now returned from comment APIs
 
 Prior to 4.0, shebang comments in a source file would not appear in the output of `sourceCode.getAllComments()` or `sourceCode.getComments()`, but they would appear in the output of `sourceCode.getTokenOrCommentBefore` as line comments. This inconsistency led to some confusion for rule developers.
 
@@ -205,13 +205,13 @@ sourceCode.getAllComments().filter(comment => comment.type !== "Shebang");
 
 ---
 
-## <a name="global-property"/> The `global` property in the `linter.verify()` API is no longer supported
+## <a name="global-property"></a> The `global` property in the `linter.verify()` API is no longer supported
 
 Previously, the `linter.verify()` API accepted a `global` config option, which was a synonym for the documented `globals` property. The `global` option was never documented or officially supported, and did not work in config files. It has been removed in 4.0.
 
 **To address:** If you were using the `global` property, please use the `globals` property instead, which does the same thing.
 
-## <a name="report-locations"/> More report messages now have full location ranges
+## <a name="report-locations"></a> More report messages now have full location ranges
 
 Starting in 3.1.0, rules have been able to specify the *end* location of a reported problem, in addition to the start location, by explicitly specifying an end location in the `report` call. This is useful for tools like editor integrations, which can use the range to precisely display where a reported problem occurs. Starting in 4.0, if a *node* is reported rather than a location, the end location of the range will automatically be inferred from the end location of the node. As a result, many more reported problems will have end locations.
 
@@ -219,7 +219,7 @@ This is not expected to cause breakage. However, it will likely result in larger
 
 **To address:** If you have an integration that deals with the ranges of reported problems, make sure you handle large report ranges in a user-friendly way.
 
-## <a name="exposed-es2015-classes"/> Some exposed APIs are now ES2015 classes
+## <a name="exposed-es2015-classes"></a> Some exposed APIs are now ES2015 classes
 
 The `CLIEngine`, `SourceCode`, and `RuleTester` modules from ESLint's Node.js API are now ES2015 classes. This will not break any documented behavior, but it does have some observable effects (for example, the methods on `CLIEngine.prototype` are now non-enumerable).
 
index cb3933641298125a083ea2a816486eec3031e582..2a640a7ad3c23b1ac9885c02e971955a03ef212a 100644 (file)
@@ -171,7 +171,7 @@ Several rules have been enhanced and now report additional errors:
 - [func-names](https://eslint.org/docs/rules/func-names) rule now recognizes function declarations in default exports.
 - [no-extra-parens](https://eslint.org/docs/rules/no-extra-parens) rule now recognizes parentheses in assignment targets.
 - [no-dupe-class-members](https://eslint.org/docs/rules/no-dupe-class-members) rule now recognizes computed keys for static class members.
-- [no-magic-number](https://eslint.org/docs/rules/no-magic-number) rule now recognizes bigint literals.
+- [no-magic-numbers](https://eslint.org/docs/rules/no-magic-numbers) rule now recognizes bigint literals.
 - [radix](https://eslint.org/docs/rules/radix) rule now recognizes invalid numbers for the second parameter of `parseInt()`.
 - [use-isnan](https://eslint.org/docs/rules/use-isnan) rule now recognizes class members by default.
 - [yoda](https://eslint.org/docs/rules/yoda) rule now recognizes bigint literals.
index dfc6bab48991d0e88e743ac8bf4cf39e04d757ee..974c8972136b97b85589168da85190e202fd127a 100644 (file)
@@ -1,6 +1,13 @@
 "use strict";
+const os = require("os");
 
-process.env.CHROME_BIN = require("puppeteer").executablePath();
+if (os.arch() === "arm64") {
+
+    // For arm64 architecture, install chromium-browser using "apt-get install chromium-browser"
+    process.env.CHROME_BIN = "/usr/bin/chromium-browser";
+} else {
+    process.env.CHROME_BIN = require("puppeteer").executablePath();
+}
 
 module.exports = function(config) {
     config.set({
diff --git a/eslint/lib/cli-engine/cascading-config-array-factory.js b/eslint/lib/cli-engine/cascading-config-array-factory.js
deleted file mode 100644 (file)
index f54605c..0000000
+++ /dev/null
@@ -1,490 +0,0 @@
-/**
- * @fileoverview `CascadingConfigArrayFactory` class.
- *
- * `CascadingConfigArrayFactory` class has a responsibility:
- *
- * 1. Handles cascading of config files.
- *
- * It provides two methods:
- *
- * - `getConfigArrayForFile(filePath)`
- *     Get the corresponded configuration of a given file. This method doesn't
- *     throw even if the given file didn't exist.
- * - `clearCache()`
- *     Clear the internal cache. You have to call this method when
- *     `additionalPluginPool` was updated if `baseConfig` or `cliConfig` depends
- *     on the additional plugins. (`CLIEngine#addPlugin()` method calls this.)
- *
- * @author Toru Nagashima <https://github.com/mysticatea>
- */
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const os = require("os");
-const path = require("path");
-const { validateConfigArray } = require("../shared/config-validator");
-const { emitDeprecationWarning } = require("../shared/deprecation-warnings");
-const { ConfigArrayFactory } = require("./config-array-factory");
-const { ConfigArray, ConfigDependency, IgnorePattern } = require("./config-array");
-const loadRules = require("./load-rules");
-const debug = require("debug")("eslint:cascading-config-array-factory");
-
-//------------------------------------------------------------------------------
-// Helpers
-//------------------------------------------------------------------------------
-
-// Define types for VSCode IntelliSense.
-/** @typedef {import("../shared/types").ConfigData} ConfigData */
-/** @typedef {import("../shared/types").Parser} Parser */
-/** @typedef {import("../shared/types").Plugin} Plugin */
-/** @typedef {ReturnType<ConfigArrayFactory["create"]>} ConfigArray */
-
-/**
- * @typedef {Object} CascadingConfigArrayFactoryOptions
- * @property {Map<string,Plugin>} [additionalPluginPool] The map for additional plugins.
- * @property {ConfigData} [baseConfig] The config by `baseConfig` option.
- * @property {ConfigData} [cliConfig] The config by CLI options (`--env`, `--global`, `--ignore-pattern`, `--parser`, `--parser-options`, `--plugin`, and `--rule`). CLI options overwrite the setting in config files.
- * @property {string} [cwd] The base directory to start lookup.
- * @property {string} [ignorePath] The path to the alternative file of `.eslintignore`.
- * @property {string[]} [rulePaths] The value of `--rulesdir` option.
- * @property {string} [specificConfigPath] The value of `--config` option.
- * @property {boolean} [useEslintrc] if `false` then it doesn't load config files.
- */
-
-/**
- * @typedef {Object} CascadingConfigArrayFactoryInternalSlots
- * @property {ConfigArray} baseConfigArray The config array of `baseConfig` option.
- * @property {ConfigData} baseConfigData The config data of `baseConfig` option. This is used to reset `baseConfigArray`.
- * @property {ConfigArray} cliConfigArray The config array of CLI options.
- * @property {ConfigData} cliConfigData The config data of CLI options. This is used to reset `cliConfigArray`.
- * @property {ConfigArrayFactory} configArrayFactory The factory for config arrays.
- * @property {Map<string, ConfigArray>} configCache The cache from directory paths to config arrays.
- * @property {string} cwd The base directory to start lookup.
- * @property {WeakMap<ConfigArray, ConfigArray>} finalizeCache The cache from config arrays to finalized config arrays.
- * @property {string} [ignorePath] The path to the alternative file of `.eslintignore`.
- * @property {string[]|null} rulePaths The value of `--rulesdir` option. This is used to reset `baseConfigArray`.
- * @property {string|null} specificConfigPath The value of `--config` option. This is used to reset `cliConfigArray`.
- * @property {boolean} useEslintrc if `false` then it doesn't load config files.
- */
-
-/** @type {WeakMap<CascadingConfigArrayFactory, CascadingConfigArrayFactoryInternalSlots>} */
-const internalSlotsMap = new WeakMap();
-
-/**
- * Create the config array from `baseConfig` and `rulePaths`.
- * @param {CascadingConfigArrayFactoryInternalSlots} slots The slots.
- * @returns {ConfigArray} The config array of the base configs.
- */
-function createBaseConfigArray({
-    configArrayFactory,
-    baseConfigData,
-    rulePaths,
-    cwd
-}) {
-    const baseConfigArray = configArrayFactory.create(
-        baseConfigData,
-        { name: "BaseConfig" }
-    );
-
-    /*
-     * Create the config array element for the default ignore patterns.
-     * This element has `ignorePattern` property that ignores the default
-     * patterns in the current working directory.
-     */
-    baseConfigArray.unshift(configArrayFactory.create(
-        { ignorePatterns: IgnorePattern.DefaultPatterns },
-        { name: "DefaultIgnorePattern" }
-    )[0]);
-
-    /*
-     * Load rules `--rulesdir` option as a pseudo plugin.
-     * Use a pseudo plugin to define rules of `--rulesdir`, so we can validate
-     * the rule's options with only information in the config array.
-     */
-    if (rulePaths && rulePaths.length > 0) {
-        baseConfigArray.push({
-            type: "config",
-            name: "--rulesdir",
-            filePath: "",
-            plugins: {
-                "": new ConfigDependency({
-                    definition: {
-                        rules: rulePaths.reduce(
-                            (map, rulesPath) => Object.assign(
-                                map,
-                                loadRules(rulesPath, cwd)
-                            ),
-                            {}
-                        )
-                    },
-                    filePath: "",
-                    id: "",
-                    importerName: "--rulesdir",
-                    importerPath: ""
-                })
-            }
-        });
-    }
-
-    return baseConfigArray;
-}
-
-/**
- * Create the config array from CLI options.
- * @param {CascadingConfigArrayFactoryInternalSlots} slots The slots.
- * @returns {ConfigArray} The config array of the base configs.
- */
-function createCLIConfigArray({
-    cliConfigData,
-    configArrayFactory,
-    cwd,
-    ignorePath,
-    specificConfigPath
-}) {
-    const cliConfigArray = configArrayFactory.create(
-        cliConfigData,
-        { name: "CLIOptions" }
-    );
-
-    cliConfigArray.unshift(
-        ...(ignorePath
-            ? configArrayFactory.loadESLintIgnore(ignorePath)
-            : configArrayFactory.loadDefaultESLintIgnore())
-    );
-
-    if (specificConfigPath) {
-        cliConfigArray.unshift(
-            ...configArrayFactory.loadFile(
-                specificConfigPath,
-                { name: "--config", basePath: cwd }
-            )
-        );
-    }
-
-    return cliConfigArray;
-}
-
-/**
- * The error type when there are files matched by a glob, but all of them have been ignored.
- */
-class ConfigurationNotFoundError extends Error {
-
-    // eslint-disable-next-line jsdoc/require-description
-    /**
-     * @param {string} directoryPath The directory path.
-     */
-    constructor(directoryPath) {
-        super(`No ESLint configuration found in ${directoryPath}.`);
-        this.messageTemplate = "no-config-found";
-        this.messageData = { directoryPath };
-    }
-}
-
-/**
- * This class provides the functionality that enumerates every file which is
- * matched by given glob patterns and that configuration.
- */
-class CascadingConfigArrayFactory {
-
-    /**
-     * Initialize this enumerator.
-     * @param {CascadingConfigArrayFactoryOptions} options The options.
-     */
-    constructor({
-        additionalPluginPool = new Map(),
-        baseConfig: baseConfigData = null,
-        cliConfig: cliConfigData = null,
-        cwd = process.cwd(),
-        ignorePath,
-        resolvePluginsRelativeTo,
-        rulePaths = [],
-        specificConfigPath = null,
-        useEslintrc = true
-    } = {}) {
-        const configArrayFactory = new ConfigArrayFactory({
-            additionalPluginPool,
-            cwd,
-            resolvePluginsRelativeTo
-        });
-
-        internalSlotsMap.set(this, {
-            baseConfigArray: createBaseConfigArray({
-                baseConfigData,
-                configArrayFactory,
-                cwd,
-                rulePaths
-            }),
-            baseConfigData,
-            cliConfigArray: createCLIConfigArray({
-                cliConfigData,
-                configArrayFactory,
-                cwd,
-                ignorePath,
-                specificConfigPath
-            }),
-            cliConfigData,
-            configArrayFactory,
-            configCache: new Map(),
-            cwd,
-            finalizeCache: new WeakMap(),
-            ignorePath,
-            rulePaths,
-            specificConfigPath,
-            useEslintrc
-        });
-    }
-
-    /**
-     * The path to the current working directory.
-     * This is used by tests.
-     * @type {string}
-     */
-    get cwd() {
-        const { cwd } = internalSlotsMap.get(this);
-
-        return cwd;
-    }
-
-    /**
-     * Get the config array of a given file.
-     * If `filePath` was not given, it returns the config which contains only
-     * `baseConfigData` and `cliConfigData`.
-     * @param {string} [filePath] The file path to a file.
-     * @param {Object} [options] The options.
-     * @param {boolean} [options.ignoreNotFoundError] If `true` then it doesn't throw `ConfigurationNotFoundError`.
-     * @returns {ConfigArray} The config array of the file.
-     */
-    getConfigArrayForFile(filePath, { ignoreNotFoundError = false } = {}) {
-        const {
-            baseConfigArray,
-            cliConfigArray,
-            cwd
-        } = internalSlotsMap.get(this);
-
-        if (!filePath) {
-            return new ConfigArray(...baseConfigArray, ...cliConfigArray);
-        }
-
-        const directoryPath = path.dirname(path.resolve(cwd, filePath));
-
-        debug(`Load config files for ${directoryPath}.`);
-
-        return this._finalizeConfigArray(
-            this._loadConfigInAncestors(directoryPath),
-            directoryPath,
-            ignoreNotFoundError
-        );
-    }
-
-    /**
-     * Set the config data to override all configs.
-     * Require to call `clearCache()` method after this method is called.
-     * @param {ConfigData} configData The config data to override all configs.
-     * @returns {void}
-     */
-    setOverrideConfig(configData) {
-        const slots = internalSlotsMap.get(this);
-
-        slots.cliConfigData = configData;
-    }
-
-    /**
-     * Clear config cache.
-     * @returns {void}
-     */
-    clearCache() {
-        const slots = internalSlotsMap.get(this);
-
-        slots.baseConfigArray = createBaseConfigArray(slots);
-        slots.cliConfigArray = createCLIConfigArray(slots);
-        slots.configCache.clear();
-    }
-
-    /**
-     * Load and normalize config files from the ancestor directories.
-     * @param {string} directoryPath The path to a leaf directory.
-     * @param {boolean} configsExistInSubdirs `true` if configurations exist in subdirectories.
-     * @returns {ConfigArray} The loaded config.
-     * @private
-     */
-    _loadConfigInAncestors(directoryPath, configsExistInSubdirs = false) {
-        const {
-            baseConfigArray,
-            configArrayFactory,
-            configCache,
-            cwd,
-            useEslintrc
-        } = internalSlotsMap.get(this);
-
-        if (!useEslintrc) {
-            return baseConfigArray;
-        }
-
-        let configArray = configCache.get(directoryPath);
-
-        // Hit cache.
-        if (configArray) {
-            debug(`Cache hit: ${directoryPath}.`);
-            return configArray;
-        }
-        debug(`No cache found: ${directoryPath}.`);
-
-        const homePath = os.homedir();
-
-        // Consider this is root.
-        if (directoryPath === homePath && cwd !== homePath) {
-            debug("Stop traversing because of considered root.");
-            if (configsExistInSubdirs) {
-                const filePath = ConfigArrayFactory.getPathToConfigFileInDirectory(directoryPath);
-
-                if (filePath) {
-                    emitDeprecationWarning(
-                        filePath,
-                        "ESLINT_PERSONAL_CONFIG_SUPPRESS"
-                    );
-                }
-            }
-            return this._cacheConfig(directoryPath, baseConfigArray);
-        }
-
-        // Load the config on this directory.
-        try {
-            configArray = configArrayFactory.loadInDirectory(directoryPath);
-        } catch (error) {
-            /* istanbul ignore next */
-            if (error.code === "EACCES") {
-                debug("Stop traversing because of 'EACCES' error.");
-                return this._cacheConfig(directoryPath, baseConfigArray);
-            }
-            throw error;
-        }
-
-        if (configArray.length > 0 && configArray.isRoot()) {
-            debug("Stop traversing because of 'root:true'.");
-            configArray.unshift(...baseConfigArray);
-            return this._cacheConfig(directoryPath, configArray);
-        }
-
-        // Load from the ancestors and merge it.
-        const parentPath = path.dirname(directoryPath);
-        const parentConfigArray = parentPath && parentPath !== directoryPath
-            ? this._loadConfigInAncestors(
-                parentPath,
-                configsExistInSubdirs || configArray.length > 0
-            )
-            : baseConfigArray;
-
-        if (configArray.length > 0) {
-            configArray.unshift(...parentConfigArray);
-        } else {
-            configArray = parentConfigArray;
-        }
-
-        // Cache and return.
-        return this._cacheConfig(directoryPath, configArray);
-    }
-
-    /**
-     * Freeze and cache a given config.
-     * @param {string} directoryPath The path to a directory as a cache key.
-     * @param {ConfigArray} configArray The config array as a cache value.
-     * @returns {ConfigArray} The `configArray` (frozen).
-     */
-    _cacheConfig(directoryPath, configArray) {
-        const { configCache } = internalSlotsMap.get(this);
-
-        Object.freeze(configArray);
-        configCache.set(directoryPath, configArray);
-
-        return configArray;
-    }
-
-    /**
-     * Finalize a given config array.
-     * Concatenate `--config` and other CLI options.
-     * @param {ConfigArray} configArray The parent config array.
-     * @param {string} directoryPath The path to the leaf directory to find config files.
-     * @param {boolean} ignoreNotFoundError If `true` then it doesn't throw `ConfigurationNotFoundError`.
-     * @returns {ConfigArray} The loaded config.
-     * @private
-     */
-    _finalizeConfigArray(configArray, directoryPath, ignoreNotFoundError) {
-        const {
-            cliConfigArray,
-            configArrayFactory,
-            finalizeCache,
-            useEslintrc
-        } = internalSlotsMap.get(this);
-
-        let finalConfigArray = finalizeCache.get(configArray);
-
-        if (!finalConfigArray) {
-            finalConfigArray = configArray;
-
-            // Load the personal config if there are no regular config files.
-            if (
-                useEslintrc &&
-                configArray.every(c => !c.filePath) &&
-                cliConfigArray.every(c => !c.filePath) // `--config` option can be a file.
-            ) {
-                const homePath = os.homedir();
-
-                debug("Loading the config file of the home directory:", homePath);
-
-                const personalConfigArray = configArrayFactory.loadInDirectory(
-                    homePath,
-                    { name: "PersonalConfig" }
-                );
-
-                if (
-                    personalConfigArray.length > 0 &&
-                    !directoryPath.startsWith(homePath)
-                ) {
-                    const lastElement =
-                        personalConfigArray[personalConfigArray.length - 1];
-
-                    emitDeprecationWarning(
-                        lastElement.filePath,
-                        "ESLINT_PERSONAL_CONFIG_LOAD"
-                    );
-                }
-
-                finalConfigArray = finalConfigArray.concat(personalConfigArray);
-            }
-
-            // Apply CLI options.
-            if (cliConfigArray.length > 0) {
-                finalConfigArray = finalConfigArray.concat(cliConfigArray);
-            }
-
-            // Validate rule settings and environments.
-            validateConfigArray(finalConfigArray);
-
-            // Cache it.
-            Object.freeze(finalConfigArray);
-            finalizeCache.set(configArray, finalConfigArray);
-
-            debug(
-                "Configuration was determined: %o on %s",
-                finalConfigArray,
-                directoryPath
-            );
-        }
-
-        // At least one element (the default ignore patterns) exists.
-        if (!ignoreNotFoundError && useEslintrc && finalConfigArray.length <= 1) {
-            throw new ConfigurationNotFoundError(directoryPath);
-        }
-
-        return finalConfigArray;
-    }
-}
-
-//------------------------------------------------------------------------------
-// Public Interface
-//------------------------------------------------------------------------------
-
-module.exports = { CascadingConfigArrayFactory };
index 802a405c04ac7222498916030379b27ec52dc022..9a414061501e2d1209bc8d19cb24336892eaa923 100644 (file)
@@ -19,14 +19,29 @@ const fs = require("fs");
 const path = require("path");
 const defaultOptions = require("../../conf/default-cli-options");
 const pkg = require("../../package.json");
-const ConfigOps = require("../shared/config-ops");
-const naming = require("../shared/naming");
-const ModuleResolver = require("../shared/relative-module-resolver");
+
+
+const {
+    Legacy: {
+        ConfigOps,
+        naming,
+        CascadingConfigArrayFactory,
+        IgnorePattern,
+        getUsedExtractedConfigs
+    }
+} = 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");
 const builtInRules = require("../rules");
-const { CascadingConfigArrayFactory } = require("./cascading-config-array-factory");
-const { IgnorePattern, getUsedExtractedConfigs } = require("./config-array");
-const { FileEnumerator } = require("./file-enumerator");
+const loadRules = require("./load-rules");
 const hash = require("./hash");
 const LintResultCache = require("./lint-result-cache");
 
@@ -559,7 +574,11 @@ class CLIEngine {
             resolvePluginsRelativeTo: options.resolvePluginsRelativeTo,
             rulePaths: options.rulePaths,
             specificConfigPath: options.configFile,
-            useEslintrc: options.useEslintrc
+            useEslintrc: options.useEslintrc,
+            builtInRules,
+            loadRules,
+            eslintRecommendedPath: path.resolve(__dirname, "../../conf/eslint-recommended.js"),
+            eslintAllPath: path.resolve(__dirname, "../../conf/eslint-all.js")
         });
         const fileEnumerator = new FileEnumerator({
             configArrayFactory,
diff --git a/eslint/lib/cli-engine/config-array-factory.js b/eslint/lib/cli-engine/config-array-factory.js
deleted file mode 100644 (file)
index 7c0fba6..0000000
+++ /dev/null
@@ -1,1074 +0,0 @@
-/**
- * @fileoverview The factory of `ConfigArray` objects.
- *
- * This class provides methods to create `ConfigArray` instance.
- *
- * - `create(configData, options)`
- *     Create a `ConfigArray` instance from a config data. This is to handle CLI
- *     options except `--config`.
- * - `loadFile(filePath, options)`
- *     Create a `ConfigArray` instance from a config file. This is to handle
- *     `--config` option. If the file was not found, throws the following error:
- *      - If the filename was `*.js`, a `MODULE_NOT_FOUND` error.
- *      - If the filename was `package.json`, an IO error or an
- *        `ESLINT_CONFIG_FIELD_NOT_FOUND` error.
- *      - Otherwise, an IO error such as `ENOENT`.
- * - `loadInDirectory(directoryPath, options)`
- *     Create a `ConfigArray` instance from a config file which is on a given
- *     directory. This tries to load `.eslintrc.*` or `package.json`. If not
- *     found, returns an empty `ConfigArray`.
- * - `loadESLintIgnore(filePath)`
- *     Create a `ConfigArray` instance from a config file that is `.eslintignore`
- *     format. This is to handle `--ignore-path` option.
- * - `loadDefaultESLintIgnore()`
- *     Create a `ConfigArray` instance from `.eslintignore` or `package.json` in
- *     the current working directory.
- *
- * `ConfigArrayFactory` class has the responsibility that loads configuration
- * files, including loading `extends`, `parser`, and `plugins`. The created
- * `ConfigArray` instance has the loaded `extends`, `parser`, and `plugins`.
- *
- * But this class doesn't handle cascading. `CascadingConfigArrayFactory` class
- * handles cascading and hierarchy.
- *
- * @author Toru Nagashima <https://github.com/mysticatea>
- */
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const fs = require("fs");
-const path = require("path");
-const importFresh = require("import-fresh");
-const stripComments = require("strip-json-comments");
-const { validateConfigSchema } = require("../shared/config-validator");
-const naming = require("../shared/naming");
-const ModuleResolver = require("../shared/relative-module-resolver");
-const {
-    ConfigArray,
-    ConfigDependency,
-    IgnorePattern,
-    OverrideTester
-} = require("./config-array");
-const debug = require("debug")("eslint:config-array-factory");
-
-//------------------------------------------------------------------------------
-// Helpers
-//------------------------------------------------------------------------------
-
-const eslintRecommendedPath = path.resolve(__dirname, "../../conf/eslint-recommended.js");
-const eslintAllPath = path.resolve(__dirname, "../../conf/eslint-all.js");
-const configFilenames = [
-    ".eslintrc.js",
-    ".eslintrc.cjs",
-    ".eslintrc.yaml",
-    ".eslintrc.yml",
-    ".eslintrc.json",
-    ".eslintrc",
-    "package.json"
-];
-
-// Define types for VSCode IntelliSense.
-/** @typedef {import("../shared/types").ConfigData} ConfigData */
-/** @typedef {import("../shared/types").OverrideConfigData} OverrideConfigData */
-/** @typedef {import("../shared/types").Parser} Parser */
-/** @typedef {import("../shared/types").Plugin} Plugin */
-/** @typedef {import("./config-array/config-dependency").DependentParser} DependentParser */
-/** @typedef {import("./config-array/config-dependency").DependentPlugin} DependentPlugin */
-/** @typedef {ConfigArray[0]} ConfigArrayElement */
-
-/**
- * @typedef {Object} ConfigArrayFactoryOptions
- * @property {Map<string,Plugin>} [additionalPluginPool] The map for additional plugins.
- * @property {string} [cwd] The path to the current working directory.
- * @property {string} [resolvePluginsRelativeTo] A path to the directory that plugins should be resolved from. Defaults to `cwd`.
- */
-
-/**
- * @typedef {Object} ConfigArrayFactoryInternalSlots
- * @property {Map<string,Plugin>} additionalPluginPool The map for additional plugins.
- * @property {string} cwd The path to the current working directory.
- * @property {string | undefined} resolvePluginsRelativeTo An absolute path the the directory that plugins should be resolved from.
- */
-
-/**
- * @typedef {Object} ConfigArrayFactoryLoadingContext
- * @property {string} filePath The path to the current configuration.
- * @property {string} matchBasePath The base path to resolve relative paths in `overrides[].files`, `overrides[].excludedFiles`, and `ignorePatterns`.
- * @property {string} name The name of the current configuration.
- * @property {string} pluginBasePath The base path to resolve plugins.
- * @property {"config" | "ignore" | "implicit-processor"} type The type of the current configuration. This is `"config"` in normal. This is `"ignore"` if it came from `.eslintignore`. This is `"implicit-processor"` if it came from legacy file-extension processors.
- */
-
-/**
- * @typedef {Object} ConfigArrayFactoryLoadingContext
- * @property {string} filePath The path to the current configuration.
- * @property {string} matchBasePath The base path to resolve relative paths in `overrides[].files`, `overrides[].excludedFiles`, and `ignorePatterns`.
- * @property {string} name The name of the current configuration.
- * @property {"config" | "ignore" | "implicit-processor"} type The type of the current configuration. This is `"config"` in normal. This is `"ignore"` if it came from `.eslintignore`. This is `"implicit-processor"` if it came from legacy file-extension processors.
- */
-
-/** @type {WeakMap<ConfigArrayFactory, ConfigArrayFactoryInternalSlots>} */
-const internalSlotsMap = new WeakMap();
-
-/**
- * Check if a given string is a file path.
- * @param {string} nameOrPath A module name or file path.
- * @returns {boolean} `true` if the `nameOrPath` is a file path.
- */
-function isFilePath(nameOrPath) {
-    return (
-        /^\.{1,2}[/\\]/u.test(nameOrPath) ||
-        path.isAbsolute(nameOrPath)
-    );
-}
-
-/**
- * Convenience wrapper for synchronously reading file contents.
- * @param {string} filePath The filename to read.
- * @returns {string} The file contents, with the BOM removed.
- * @private
- */
-function readFile(filePath) {
-    return fs.readFileSync(filePath, "utf8").replace(/^\ufeff/u, "");
-}
-
-/**
- * Loads a YAML configuration from a file.
- * @param {string} filePath The filename to load.
- * @returns {ConfigData} The configuration object from the file.
- * @throws {Error} If the file cannot be read.
- * @private
- */
-function loadYAMLConfigFile(filePath) {
-    debug(`Loading YAML config file: ${filePath}`);
-
-    // lazy load YAML to improve performance when not used
-    const yaml = require("js-yaml");
-
-    try {
-
-        // empty YAML file can be null, so always use
-        return yaml.safeLoad(readFile(filePath)) || {};
-    } catch (e) {
-        debug(`Error reading YAML file: ${filePath}`);
-        e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`;
-        throw e;
-    }
-}
-
-/**
- * Loads a JSON configuration from a file.
- * @param {string} filePath The filename to load.
- * @returns {ConfigData} The configuration object from the file.
- * @throws {Error} If the file cannot be read.
- * @private
- */
-function loadJSONConfigFile(filePath) {
-    debug(`Loading JSON config file: ${filePath}`);
-
-    try {
-        return JSON.parse(stripComments(readFile(filePath)));
-    } catch (e) {
-        debug(`Error reading JSON file: ${filePath}`);
-        e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`;
-        e.messageTemplate = "failed-to-read-json";
-        e.messageData = {
-            path: filePath,
-            message: e.message
-        };
-        throw e;
-    }
-}
-
-/**
- * Loads a legacy (.eslintrc) configuration from a file.
- * @param {string} filePath The filename to load.
- * @returns {ConfigData} The configuration object from the file.
- * @throws {Error} If the file cannot be read.
- * @private
- */
-function loadLegacyConfigFile(filePath) {
-    debug(`Loading legacy config file: ${filePath}`);
-
-    // lazy load YAML to improve performance when not used
-    const yaml = require("js-yaml");
-
-    try {
-        return yaml.safeLoad(stripComments(readFile(filePath))) || /* istanbul ignore next */ {};
-    } catch (e) {
-        debug("Error reading YAML file: %s\n%o", filePath, e);
-        e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`;
-        throw e;
-    }
-}
-
-/**
- * Loads a JavaScript configuration from a file.
- * @param {string} filePath The filename to load.
- * @returns {ConfigData} The configuration object from the file.
- * @throws {Error} If the file cannot be read.
- * @private
- */
-function loadJSConfigFile(filePath) {
-    debug(`Loading JS config file: ${filePath}`);
-    try {
-        return importFresh(filePath);
-    } catch (e) {
-        debug(`Error reading JavaScript file: ${filePath}`);
-        e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`;
-        throw e;
-    }
-}
-
-/**
- * Loads a configuration from a package.json file.
- * @param {string} filePath The filename to load.
- * @returns {ConfigData} The configuration object from the file.
- * @throws {Error} If the file cannot be read.
- * @private
- */
-function loadPackageJSONConfigFile(filePath) {
-    debug(`Loading package.json config file: ${filePath}`);
-    try {
-        const packageData = loadJSONConfigFile(filePath);
-
-        if (!Object.hasOwnProperty.call(packageData, "eslintConfig")) {
-            throw Object.assign(
-                new Error("package.json file doesn't have 'eslintConfig' field."),
-                { code: "ESLINT_CONFIG_FIELD_NOT_FOUND" }
-            );
-        }
-
-        return packageData.eslintConfig;
-    } catch (e) {
-        debug(`Error reading package.json file: ${filePath}`);
-        e.message = `Cannot read config file: ${filePath}\nError: ${e.message}`;
-        throw e;
-    }
-}
-
-/**
- * Loads a `.eslintignore` from a file.
- * @param {string} filePath The filename to load.
- * @returns {string[]} The ignore patterns from the file.
- * @private
- */
-function loadESLintIgnoreFile(filePath) {
-    debug(`Loading .eslintignore file: ${filePath}`);
-
-    try {
-        return readFile(filePath)
-            .split(/\r?\n/gu)
-            .filter(line => line.trim() !== "" && !line.startsWith("#"));
-    } catch (e) {
-        debug(`Error reading .eslintignore file: ${filePath}`);
-        e.message = `Cannot read .eslintignore file: ${filePath}\nError: ${e.message}`;
-        throw e;
-    }
-}
-
-/**
- * Creates an error to notify about a missing config to extend from.
- * @param {string} configName The name of the missing config.
- * @param {string} importerName The name of the config that imported the missing config
- * @returns {Error} The error object to throw
- * @private
- */
-function configMissingError(configName, importerName) {
-    return Object.assign(
-        new Error(`Failed to load config "${configName}" to extend from.`),
-        {
-            messageTemplate: "extend-config-missing",
-            messageData: { configName, importerName }
-        }
-    );
-}
-
-/**
- * Loads a configuration file regardless of the source. Inspects the file path
- * to determine the correctly way to load the config file.
- * @param {string} filePath The path to the configuration.
- * @returns {ConfigData|null} The configuration information.
- * @private
- */
-function loadConfigFile(filePath) {
-    switch (path.extname(filePath)) {
-        case ".js":
-        case ".cjs":
-            return loadJSConfigFile(filePath);
-
-        case ".json":
-            if (path.basename(filePath) === "package.json") {
-                return loadPackageJSONConfigFile(filePath);
-            }
-            return loadJSONConfigFile(filePath);
-
-        case ".yaml":
-        case ".yml":
-            return loadYAMLConfigFile(filePath);
-
-        default:
-            return loadLegacyConfigFile(filePath);
-    }
-}
-
-/**
- * Write debug log.
- * @param {string} request The requested module name.
- * @param {string} relativeTo The file path to resolve the request relative to.
- * @param {string} filePath The resolved file path.
- * @returns {void}
- */
-function writeDebugLogForLoading(request, relativeTo, filePath) {
-    /* istanbul ignore next */
-    if (debug.enabled) {
-        let nameAndVersion = null;
-
-        try {
-            const packageJsonPath = ModuleResolver.resolve(
-                `${request}/package.json`,
-                relativeTo
-            );
-            const { version = "unknown" } = require(packageJsonPath);
-
-            nameAndVersion = `${request}@${version}`;
-        } catch (error) {
-            debug("package.json was not found:", error.message);
-            nameAndVersion = request;
-        }
-
-        debug("Loaded: %s (%s)", nameAndVersion, filePath);
-    }
-}
-
-/**
- * Create a new context with default values.
- * @param {ConfigArrayFactoryInternalSlots} slots The internal slots.
- * @param {"config" | "ignore" | "implicit-processor" | undefined} providedType The type of the current configuration. Default is `"config"`.
- * @param {string | undefined} providedName The name of the current configuration. Default is the relative path from `cwd` to `filePath`.
- * @param {string | undefined} providedFilePath The path to the current configuration. Default is empty string.
- * @param {string | undefined} providedMatchBasePath The type of the current configuration. Default is the directory of `filePath` or `cwd`.
- * @returns {ConfigArrayFactoryLoadingContext} The created context.
- */
-function createContext(
-    { cwd, resolvePluginsRelativeTo },
-    providedType,
-    providedName,
-    providedFilePath,
-    providedMatchBasePath
-) {
-    const filePath = providedFilePath
-        ? path.resolve(cwd, providedFilePath)
-        : "";
-    const matchBasePath =
-        (providedMatchBasePath && path.resolve(cwd, providedMatchBasePath)) ||
-        (filePath && path.dirname(filePath)) ||
-        cwd;
-    const name =
-        providedName ||
-        (filePath && path.relative(cwd, filePath)) ||
-        "";
-    const pluginBasePath =
-        resolvePluginsRelativeTo ||
-        (filePath && path.dirname(filePath)) ||
-        cwd;
-    const type = providedType || "config";
-
-    return { filePath, matchBasePath, name, pluginBasePath, type };
-}
-
-/**
- * Normalize a given plugin.
- * - Ensure the object to have four properties: configs, environments, processors, and rules.
- * - Ensure the object to not have other properties.
- * @param {Plugin} plugin The plugin to normalize.
- * @returns {Plugin} The normalized plugin.
- */
-function normalizePlugin(plugin) {
-    return {
-        configs: plugin.configs || {},
-        environments: plugin.environments || {},
-        processors: plugin.processors || {},
-        rules: plugin.rules || {}
-    };
-}
-
-//------------------------------------------------------------------------------
-// Public Interface
-//------------------------------------------------------------------------------
-
-/**
- * The factory of `ConfigArray` objects.
- */
-class ConfigArrayFactory {
-
-    /**
-     * Initialize this instance.
-     * @param {ConfigArrayFactoryOptions} [options] The map for additional plugins.
-     */
-    constructor({
-        additionalPluginPool = new Map(),
-        cwd = process.cwd(),
-        resolvePluginsRelativeTo
-    } = {}) {
-        internalSlotsMap.set(this, {
-            additionalPluginPool,
-            cwd,
-            resolvePluginsRelativeTo:
-                resolvePluginsRelativeTo &&
-                path.resolve(cwd, resolvePluginsRelativeTo)
-        });
-    }
-
-    /**
-     * Create `ConfigArray` instance from a config data.
-     * @param {ConfigData|null} configData The config data to create.
-     * @param {Object} [options] The options.
-     * @param {string} [options.basePath] The base path to resolve relative paths in `overrides[].files`, `overrides[].excludedFiles`, and `ignorePatterns`.
-     * @param {string} [options.filePath] The path to this config data.
-     * @param {string} [options.name] The config name.
-     * @returns {ConfigArray} Loaded config.
-     */
-    create(configData, { basePath, filePath, name } = {}) {
-        if (!configData) {
-            return new ConfigArray();
-        }
-
-        const slots = internalSlotsMap.get(this);
-        const ctx = createContext(slots, "config", name, filePath, basePath);
-        const elements = this._normalizeConfigData(configData, ctx);
-
-        return new ConfigArray(...elements);
-    }
-
-    /**
-     * Load a config file.
-     * @param {string} filePath The path to a config file.
-     * @param {Object} [options] The options.
-     * @param {string} [options.basePath] The base path to resolve relative paths in `overrides[].files`, `overrides[].excludedFiles`, and `ignorePatterns`.
-     * @param {string} [options.name] The config name.
-     * @returns {ConfigArray} Loaded config.
-     */
-    loadFile(filePath, { basePath, name } = {}) {
-        const slots = internalSlotsMap.get(this);
-        const ctx = createContext(slots, "config", name, filePath, basePath);
-
-        return new ConfigArray(...this._loadConfigData(ctx));
-    }
-
-    /**
-     * Load the config file on a given directory if exists.
-     * @param {string} directoryPath The path to a directory.
-     * @param {Object} [options] The options.
-     * @param {string} [options.basePath] The base path to resolve relative paths in `overrides[].files`, `overrides[].excludedFiles`, and `ignorePatterns`.
-     * @param {string} [options.name] The config name.
-     * @returns {ConfigArray} Loaded config. An empty `ConfigArray` if any config doesn't exist.
-     */
-    loadInDirectory(directoryPath, { basePath, name } = {}) {
-        const slots = internalSlotsMap.get(this);
-
-        for (const filename of configFilenames) {
-            const ctx = createContext(
-                slots,
-                "config",
-                name,
-                path.join(directoryPath, filename),
-                basePath
-            );
-
-            if (fs.existsSync(ctx.filePath)) {
-                let configData;
-
-                try {
-                    configData = loadConfigFile(ctx.filePath);
-                } catch (error) {
-                    if (!error || error.code !== "ESLINT_CONFIG_FIELD_NOT_FOUND") {
-                        throw error;
-                    }
-                }
-
-                if (configData) {
-                    debug(`Config file found: ${ctx.filePath}`);
-                    return new ConfigArray(
-                        ...this._normalizeConfigData(configData, ctx)
-                    );
-                }
-            }
-        }
-
-        debug(`Config file not found on ${directoryPath}`);
-        return new ConfigArray();
-    }
-
-    /**
-     * Check if a config file on a given directory exists or not.
-     * @param {string} directoryPath The path to a directory.
-     * @returns {string | null} The path to the found config file. If not found then null.
-     */
-    static getPathToConfigFileInDirectory(directoryPath) {
-        for (const filename of configFilenames) {
-            const filePath = path.join(directoryPath, filename);
-
-            if (fs.existsSync(filePath)) {
-                if (filename === "package.json") {
-                    try {
-                        loadPackageJSONConfigFile(filePath);
-                        return filePath;
-                    } catch { /* ignore */ }
-                } else {
-                    return filePath;
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Load `.eslintignore` file.
-     * @param {string} filePath The path to a `.eslintignore` file to load.
-     * @returns {ConfigArray} Loaded config. An empty `ConfigArray` if any config doesn't exist.
-     */
-    loadESLintIgnore(filePath) {
-        const slots = internalSlotsMap.get(this);
-        const ctx = createContext(
-            slots,
-            "ignore",
-            void 0,
-            filePath,
-            slots.cwd
-        );
-        const ignorePatterns = loadESLintIgnoreFile(ctx.filePath);
-
-        return new ConfigArray(
-            ...this._normalizeESLintIgnoreData(ignorePatterns, ctx)
-        );
-    }
-
-    /**
-     * Load `.eslintignore` file in the current working directory.
-     * @returns {ConfigArray} Loaded config. An empty `ConfigArray` if any config doesn't exist.
-     */
-    loadDefaultESLintIgnore() {
-        const slots = internalSlotsMap.get(this);
-        const eslintIgnorePath = path.resolve(slots.cwd, ".eslintignore");
-        const packageJsonPath = path.resolve(slots.cwd, "package.json");
-
-        if (fs.existsSync(eslintIgnorePath)) {
-            return this.loadESLintIgnore(eslintIgnorePath);
-        }
-        if (fs.existsSync(packageJsonPath)) {
-            const data = loadJSONConfigFile(packageJsonPath);
-
-            if (Object.hasOwnProperty.call(data, "eslintIgnore")) {
-                if (!Array.isArray(data.eslintIgnore)) {
-                    throw new Error("Package.json eslintIgnore property requires an array of paths");
-                }
-                const ctx = createContext(
-                    slots,
-                    "ignore",
-                    "eslintIgnore in package.json",
-                    packageJsonPath,
-                    slots.cwd
-                );
-
-                return new ConfigArray(
-                    ...this._normalizeESLintIgnoreData(data.eslintIgnore, ctx)
-                );
-            }
-        }
-
-        return new ConfigArray();
-    }
-
-    /**
-     * Load a given config file.
-     * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
-     * @returns {IterableIterator<ConfigArrayElement>} Loaded config.
-     * @private
-     */
-    _loadConfigData(ctx) {
-        return this._normalizeConfigData(loadConfigFile(ctx.filePath), ctx);
-    }
-
-    /**
-     * Normalize a given `.eslintignore` data to config array elements.
-     * @param {string[]} ignorePatterns The patterns to ignore files.
-     * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
-     * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
-     * @private
-     */
-    *_normalizeESLintIgnoreData(ignorePatterns, ctx) {
-        const elements = this._normalizeObjectConfigData(
-            { ignorePatterns },
-            ctx
-        );
-
-        // Set `ignorePattern.loose` flag for backward compatibility.
-        for (const element of elements) {
-            if (element.ignorePattern) {
-                element.ignorePattern.loose = true;
-            }
-            yield element;
-        }
-    }
-
-    /**
-     * Normalize a given config to an array.
-     * @param {ConfigData} configData The config data to normalize.
-     * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
-     * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
-     * @private
-     */
-    _normalizeConfigData(configData, ctx) {
-        validateConfigSchema(configData, ctx.name || ctx.filePath);
-        return this._normalizeObjectConfigData(configData, ctx);
-    }
-
-    /**
-     * Normalize a given config to an array.
-     * @param {ConfigData|OverrideConfigData} configData The config data to normalize.
-     * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
-     * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
-     * @private
-     */
-    *_normalizeObjectConfigData(configData, ctx) {
-        const { files, excludedFiles, ...configBody } = configData;
-        const criteria = OverrideTester.create(
-            files,
-            excludedFiles,
-            ctx.matchBasePath
-        );
-        const elements = this._normalizeObjectConfigDataBody(configBody, ctx);
-
-        // Apply the criteria to every element.
-        for (const element of elements) {
-
-            /*
-             * Merge the criteria.
-             * This is for the `overrides` entries that came from the
-             * configurations of `overrides[].extends`.
-             */
-            element.criteria = OverrideTester.and(criteria, element.criteria);
-
-            /*
-             * Remove `root` property to ignore `root` settings which came from
-             * `extends` in `overrides`.
-             */
-            if (element.criteria) {
-                element.root = void 0;
-            }
-
-            yield element;
-        }
-    }
-
-    /**
-     * Normalize a given config to an array.
-     * @param {ConfigData} configData The config data to normalize.
-     * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
-     * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
-     * @private
-     */
-    *_normalizeObjectConfigDataBody(
-        {
-            env,
-            extends: extend,
-            globals,
-            ignorePatterns,
-            noInlineConfig,
-            parser: parserName,
-            parserOptions,
-            plugins: pluginList,
-            processor,
-            reportUnusedDisableDirectives,
-            root,
-            rules,
-            settings,
-            overrides: overrideList = []
-        },
-        ctx
-    ) {
-        const extendList = Array.isArray(extend) ? extend : [extend];
-        const ignorePattern = ignorePatterns && new IgnorePattern(
-            Array.isArray(ignorePatterns) ? ignorePatterns : [ignorePatterns],
-            ctx.matchBasePath
-        );
-
-        // Flatten `extends`.
-        for (const extendName of extendList.filter(Boolean)) {
-            yield* this._loadExtends(extendName, ctx);
-        }
-
-        // Load parser & plugins.
-        const parser = parserName && this._loadParser(parserName, ctx);
-        const plugins = pluginList && this._loadPlugins(pluginList, ctx);
-
-        // Yield pseudo config data for file extension processors.
-        if (plugins) {
-            yield* this._takeFileExtensionProcessors(plugins, ctx);
-        }
-
-        // Yield the config data except `extends` and `overrides`.
-        yield {
-
-            // Debug information.
-            type: ctx.type,
-            name: ctx.name,
-            filePath: ctx.filePath,
-
-            // Config data.
-            criteria: null,
-            env,
-            globals,
-            ignorePattern,
-            noInlineConfig,
-            parser,
-            parserOptions,
-            plugins,
-            processor,
-            reportUnusedDisableDirectives,
-            root,
-            rules,
-            settings
-        };
-
-        // Flatten `overries`.
-        for (let i = 0; i < overrideList.length; ++i) {
-            yield* this._normalizeObjectConfigData(
-                overrideList[i],
-                { ...ctx, name: `${ctx.name}#overrides[${i}]` }
-            );
-        }
-    }
-
-    /**
-     * Load configs of an element in `extends`.
-     * @param {string} extendName The name of a base config.
-     * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
-     * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
-     * @private
-     */
-    _loadExtends(extendName, ctx) {
-        debug("Loading {extends:%j} relative to %s", extendName, ctx.filePath);
-        try {
-            if (extendName.startsWith("eslint:")) {
-                return this._loadExtendedBuiltInConfig(extendName, ctx);
-            }
-            if (extendName.startsWith("plugin:")) {
-                return this._loadExtendedPluginConfig(extendName, ctx);
-            }
-            return this._loadExtendedShareableConfig(extendName, ctx);
-        } catch (error) {
-            error.message += `\nReferenced from: ${ctx.filePath || ctx.name}`;
-            throw error;
-        }
-    }
-
-    /**
-     * Load configs of an element in `extends`.
-     * @param {string} extendName The name of a base config.
-     * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
-     * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
-     * @private
-     */
-    _loadExtendedBuiltInConfig(extendName, ctx) {
-        if (extendName === "eslint:recommended") {
-            return this._loadConfigData({
-                ...ctx,
-                filePath: eslintRecommendedPath,
-                name: `${ctx.name} » ${extendName}`
-            });
-        }
-        if (extendName === "eslint:all") {
-            return this._loadConfigData({
-                ...ctx,
-                filePath: eslintAllPath,
-                name: `${ctx.name} » ${extendName}`
-            });
-        }
-
-        throw configMissingError(extendName, ctx.name);
-    }
-
-    /**
-     * Load configs of an element in `extends`.
-     * @param {string} extendName The name of a base config.
-     * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
-     * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
-     * @private
-     */
-    _loadExtendedPluginConfig(extendName, ctx) {
-        const slashIndex = extendName.lastIndexOf("/");
-        const pluginName = extendName.slice("plugin:".length, slashIndex);
-        const configName = extendName.slice(slashIndex + 1);
-
-        if (isFilePath(pluginName)) {
-            throw new Error("'extends' cannot use a file path for plugins.");
-        }
-
-        const plugin = this._loadPlugin(pluginName, ctx);
-        const configData =
-            plugin.definition &&
-            plugin.definition.configs[configName];
-
-        if (configData) {
-            return this._normalizeConfigData(configData, {
-                ...ctx,
-                filePath: plugin.filePath || ctx.filePath,
-                name: `${ctx.name} » plugin:${plugin.id}/${configName}`
-            });
-        }
-
-        throw plugin.error || configMissingError(extendName, ctx.filePath);
-    }
-
-    /**
-     * Load configs of an element in `extends`.
-     * @param {string} extendName The name of a base config.
-     * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
-     * @returns {IterableIterator<ConfigArrayElement>} The normalized config.
-     * @private
-     */
-    _loadExtendedShareableConfig(extendName, ctx) {
-        const { cwd } = internalSlotsMap.get(this);
-        const relativeTo = ctx.filePath || path.join(cwd, "__placeholder__.js");
-        let request;
-
-        if (isFilePath(extendName)) {
-            request = extendName;
-        } else if (extendName.startsWith(".")) {
-            request = `./${extendName}`; // For backward compatibility. A ton of tests depended on this behavior.
-        } else {
-            request = naming.normalizePackageName(
-                extendName,
-                "eslint-config"
-            );
-        }
-
-        let filePath;
-
-        try {
-            filePath = ModuleResolver.resolve(request, relativeTo);
-        } catch (error) {
-            /* istanbul ignore else */
-            if (error && error.code === "MODULE_NOT_FOUND") {
-                throw configMissingError(extendName, ctx.filePath);
-            }
-            throw error;
-        }
-
-        writeDebugLogForLoading(request, relativeTo, filePath);
-        return this._loadConfigData({
-            ...ctx,
-            filePath,
-            name: `${ctx.name} » ${request}`
-        });
-    }
-
-    /**
-     * Load given plugins.
-     * @param {string[]} names The plugin names to load.
-     * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
-     * @returns {Record<string,DependentPlugin>} The loaded parser.
-     * @private
-     */
-    _loadPlugins(names, ctx) {
-        return names.reduce((map, name) => {
-            if (isFilePath(name)) {
-                throw new Error("Plugins array cannot includes file paths.");
-            }
-            const plugin = this._loadPlugin(name, ctx);
-
-            map[plugin.id] = plugin;
-
-            return map;
-        }, {});
-    }
-
-    /**
-     * Load a given parser.
-     * @param {string} nameOrPath The package name or the path to a parser file.
-     * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
-     * @returns {DependentParser} The loaded parser.
-     */
-    _loadParser(nameOrPath, ctx) {
-        debug("Loading parser %j from %s", nameOrPath, ctx.filePath);
-
-        const { cwd } = internalSlotsMap.get(this);
-        const relativeTo = ctx.filePath || path.join(cwd, "__placeholder__.js");
-
-        try {
-            const filePath = ModuleResolver.resolve(nameOrPath, relativeTo);
-
-            writeDebugLogForLoading(nameOrPath, relativeTo, filePath);
-
-            return new ConfigDependency({
-                definition: require(filePath),
-                filePath,
-                id: nameOrPath,
-                importerName: ctx.name,
-                importerPath: ctx.filePath
-            });
-        } catch (error) {
-
-            // If the parser name is "espree", load the espree of ESLint.
-            if (nameOrPath === "espree") {
-                debug("Fallback espree.");
-                return new ConfigDependency({
-                    definition: require("espree"),
-                    filePath: require.resolve("espree"),
-                    id: nameOrPath,
-                    importerName: ctx.name,
-                    importerPath: ctx.filePath
-                });
-            }
-
-            debug("Failed to load parser '%s' declared in '%s'.", nameOrPath, ctx.name);
-            error.message = `Failed to load parser '${nameOrPath}' declared in '${ctx.name}': ${error.message}`;
-
-            return new ConfigDependency({
-                error,
-                id: nameOrPath,
-                importerName: ctx.name,
-                importerPath: ctx.filePath
-            });
-        }
-    }
-
-    /**
-     * Load a given plugin.
-     * @param {string} name The plugin name to load.
-     * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
-     * @returns {DependentPlugin} The loaded plugin.
-     * @private
-     */
-    _loadPlugin(name, ctx) {
-        debug("Loading plugin %j from %s", name, ctx.filePath);
-
-        const { additionalPluginPool } = internalSlotsMap.get(this);
-        const request = naming.normalizePackageName(name, "eslint-plugin");
-        const id = naming.getShorthandName(request, "eslint-plugin");
-        const relativeTo = path.join(ctx.pluginBasePath, "__placeholder__.js");
-
-        if (name.match(/\s+/u)) {
-            const error = Object.assign(
-                new Error(`Whitespace found in plugin name '${name}'`),
-                {
-                    messageTemplate: "whitespace-found",
-                    messageData: { pluginName: request }
-                }
-            );
-
-            return new ConfigDependency({
-                error,
-                id,
-                importerName: ctx.name,
-                importerPath: ctx.filePath
-            });
-        }
-
-        // Check for additional pool.
-        const plugin =
-            additionalPluginPool.get(request) ||
-            additionalPluginPool.get(id);
-
-        if (plugin) {
-            return new ConfigDependency({
-                definition: normalizePlugin(plugin),
-                filePath: "", // It's unknown where the plugin came from.
-                id,
-                importerName: ctx.name,
-                importerPath: ctx.filePath
-            });
-        }
-
-        let filePath;
-        let error;
-
-        try {
-            filePath = ModuleResolver.resolve(request, relativeTo);
-        } catch (resolveError) {
-            error = resolveError;
-            /* istanbul ignore else */
-            if (error && error.code === "MODULE_NOT_FOUND") {
-                error.messageTemplate = "plugin-missing";
-                error.messageData = {
-                    pluginName: request,
-                    resolvePluginsRelativeTo: ctx.pluginBasePath,
-                    importerName: ctx.name
-                };
-            }
-        }
-
-        if (filePath) {
-            try {
-                writeDebugLogForLoading(request, relativeTo, filePath);
-
-                const startTime = Date.now();
-                const pluginDefinition = require(filePath);
-
-                debug(`Plugin ${filePath} loaded in: ${Date.now() - startTime}ms`);
-
-                return new ConfigDependency({
-                    definition: normalizePlugin(pluginDefinition),
-                    filePath,
-                    id,
-                    importerName: ctx.name,
-                    importerPath: ctx.filePath
-                });
-            } catch (loadError) {
-                error = loadError;
-            }
-        }
-
-        debug("Failed to load plugin '%s' declared in '%s'.", name, ctx.name);
-        error.message = `Failed to load plugin '${name}' declared in '${ctx.name}': ${error.message}`;
-        return new ConfigDependency({
-            error,
-            id,
-            importerName: ctx.name,
-            importerPath: ctx.filePath
-        });
-    }
-
-    /**
-     * Take file expression processors as config array elements.
-     * @param {Record<string,DependentPlugin>} plugins The plugin definitions.
-     * @param {ConfigArrayFactoryLoadingContext} ctx The loading context.
-     * @returns {IterableIterator<ConfigArrayElement>} The config array elements of file expression processors.
-     * @private
-     */
-    *_takeFileExtensionProcessors(plugins, ctx) {
-        for (const pluginId of Object.keys(plugins)) {
-            const processors =
-                plugins[pluginId] &&
-                plugins[pluginId].definition &&
-                plugins[pluginId].definition.processors;
-
-            if (!processors) {
-                continue;
-            }
-
-            for (const processorId of Object.keys(processors)) {
-                if (processorId.startsWith(".")) {
-                    yield* this._normalizeObjectConfigData(
-                        {
-                            files: [`*${processorId}`],
-                            processor: `${pluginId}/${processorId}`
-                        },
-                        {
-                            ...ctx,
-                            type: "implicit-processor",
-                            name: `${ctx.name}#processors["${pluginId}/${processorId}"]`
-                        }
-                    );
-                }
-            }
-        }
-    }
-}
-
-module.exports = { ConfigArrayFactory, createContext };
diff --git a/eslint/lib/cli-engine/config-array/config-array.js b/eslint/lib/cli-engine/config-array/config-array.js
deleted file mode 100644 (file)
index 42a7362..0000000
+++ /dev/null
@@ -1,524 +0,0 @@
-/**
- * @fileoverview `ConfigArray` class.
- *
- * `ConfigArray` class expresses the full of a configuration. It has the entry
- * config file, base config files that were extended, loaded parsers, and loaded
- * plugins.
- *
- * `ConfigArray` class provides three properties and two methods.
- *
- * - `pluginEnvironments`
- * - `pluginProcessors`
- * - `pluginRules`
- *      The `Map` objects that contain the members of all plugins that this
- *      config array contains. Those map objects don't have mutation methods.
- *      Those keys are the member ID such as `pluginId/memberName`.
- * - `isRoot()`
- *      If `true` then this configuration has `root:true` property.
- * - `extractConfig(filePath)`
- *      Extract the final configuration for a given file. This means merging
- *      every config array element which that `criteria` property matched. The
- *      `filePath` argument must be an absolute path.
- *
- * `ConfigArrayFactory` provides the loading logic of config files.
- *
- * @author Toru Nagashima <https://github.com/mysticatea>
- */
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const { ExtractedConfig } = require("./extracted-config");
-const { IgnorePattern } = require("./ignore-pattern");
-
-//------------------------------------------------------------------------------
-// Helpers
-//------------------------------------------------------------------------------
-
-// Define types for VSCode IntelliSense.
-/** @typedef {import("../../shared/types").Environment} Environment */
-/** @typedef {import("../../shared/types").GlobalConf} GlobalConf */
-/** @typedef {import("../../shared/types").RuleConf} RuleConf */
-/** @typedef {import("../../shared/types").Rule} Rule */
-/** @typedef {import("../../shared/types").Plugin} Plugin */
-/** @typedef {import("../../shared/types").Processor} Processor */
-/** @typedef {import("./config-dependency").DependentParser} DependentParser */
-/** @typedef {import("./config-dependency").DependentPlugin} DependentPlugin */
-/** @typedef {import("./override-tester")["OverrideTester"]} OverrideTester */
-
-/**
- * @typedef {Object} ConfigArrayElement
- * @property {string} name The name of this config element.
- * @property {string} filePath The path to the source file of this config element.
- * @property {InstanceType<OverrideTester>|null} criteria The tester for the `files` and `excludedFiles` of this config element.
- * @property {Record<string, boolean>|undefined} env The environment settings.
- * @property {Record<string, GlobalConf>|undefined} globals The global variable settings.
- * @property {IgnorePattern|undefined} ignorePattern The ignore patterns.
- * @property {boolean|undefined} noInlineConfig The flag that disables directive comments.
- * @property {DependentParser|undefined} parser The parser loader.
- * @property {Object|undefined} parserOptions The parser options.
- * @property {Record<string, DependentPlugin>|undefined} plugins The plugin loaders.
- * @property {string|undefined} processor The processor name to refer plugin's processor.
- * @property {boolean|undefined} reportUnusedDisableDirectives The flag to report unused `eslint-disable` comments.
- * @property {boolean|undefined} root The flag to express root.
- * @property {Record<string, RuleConf>|undefined} rules The rule settings
- * @property {Object|undefined} settings The shared settings.
- * @property {"config" | "ignore" | "implicit-processor"} type The element type.
- */
-
-/**
- * @typedef {Object} ConfigArrayInternalSlots
- * @property {Map<string, ExtractedConfig>} cache The cache to extract configs.
- * @property {ReadonlyMap<string, Environment>|null} envMap The map from environment ID to environment definition.
- * @property {ReadonlyMap<string, Processor>|null} processorMap The map from processor ID to environment definition.
- * @property {ReadonlyMap<string, Rule>|null} ruleMap The map from rule ID to rule definition.
- */
-
-/** @type {WeakMap<ConfigArray, ConfigArrayInternalSlots>} */
-const internalSlotsMap = new class extends WeakMap {
-    get(key) {
-        let value = super.get(key);
-
-        if (!value) {
-            value = {
-                cache: new Map(),
-                envMap: null,
-                processorMap: null,
-                ruleMap: null
-            };
-            super.set(key, value);
-        }
-
-        return value;
-    }
-}();
-
-/**
- * Get the indices which are matched to a given file.
- * @param {ConfigArrayElement[]} elements The elements.
- * @param {string} filePath The path to a target file.
- * @returns {number[]} The indices.
- */
-function getMatchedIndices(elements, filePath) {
-    const indices = [];
-
-    for (let i = elements.length - 1; i >= 0; --i) {
-        const element = elements[i];
-
-        if (!element.criteria || (filePath && element.criteria.test(filePath))) {
-            indices.push(i);
-        }
-    }
-
-    return indices;
-}
-
-/**
- * Check if a value is a non-null object.
- * @param {any} x The value to check.
- * @returns {boolean} `true` if the value is a non-null object.
- */
-function isNonNullObject(x) {
-    return typeof x === "object" && x !== null;
-}
-
-/**
- * Merge two objects.
- *
- * Assign every property values of `y` to `x` if `x` doesn't have the property.
- * If `x`'s property value is an object, it does recursive.
- * @param {Object} target The destination to merge
- * @param {Object|undefined} source The source to merge.
- * @returns {void}
- */
-function mergeWithoutOverwrite(target, source) {
-    if (!isNonNullObject(source)) {
-        return;
-    }
-
-    for (const key of Object.keys(source)) {
-        if (key === "__proto__") {
-            continue;
-        }
-
-        if (isNonNullObject(target[key])) {
-            mergeWithoutOverwrite(target[key], source[key]);
-        } else if (target[key] === void 0) {
-            if (isNonNullObject(source[key])) {
-                target[key] = Array.isArray(source[key]) ? [] : {};
-                mergeWithoutOverwrite(target[key], source[key]);
-            } else if (source[key] !== void 0) {
-                target[key] = source[key];
-            }
-        }
-    }
-}
-
-/**
- * The error for plugin conflicts.
- */
-class PluginConflictError extends Error {
-
-    /**
-     * Initialize this error object.
-     * @param {string} pluginId The plugin ID.
-     * @param {{filePath:string, importerName:string}[]} plugins The resolved plugins.
-     */
-    constructor(pluginId, plugins) {
-        super(`Plugin "${pluginId}" was conflicted between ${plugins.map(p => `"${p.importerName}"`).join(" and ")}.`);
-        this.messageTemplate = "plugin-conflict";
-        this.messageData = { pluginId, plugins };
-    }
-}
-
-/**
- * Merge plugins.
- * `target`'s definition is prior to `source`'s.
- * @param {Record<string, DependentPlugin>} target The destination to merge
- * @param {Record<string, DependentPlugin>|undefined} source The source to merge.
- * @returns {void}
- */
-function mergePlugins(target, source) {
-    if (!isNonNullObject(source)) {
-        return;
-    }
-
-    for (const key of Object.keys(source)) {
-        if (key === "__proto__") {
-            continue;
-        }
-        const targetValue = target[key];
-        const sourceValue = source[key];
-
-        // Adopt the plugin which was found at first.
-        if (targetValue === void 0) {
-            if (sourceValue.error) {
-                throw sourceValue.error;
-            }
-            target[key] = sourceValue;
-        } else if (sourceValue.filePath !== targetValue.filePath) {
-            throw new PluginConflictError(key, [
-                {
-                    filePath: targetValue.filePath,
-                    importerName: targetValue.importerName
-                },
-                {
-                    filePath: sourceValue.filePath,
-                    importerName: sourceValue.importerName
-                }
-            ]);
-        }
-    }
-}
-
-/**
- * Merge rule configs.
- * `target`'s definition is prior to `source`'s.
- * @param {Record<string, Array>} target The destination to merge
- * @param {Record<string, RuleConf>|undefined} source The source to merge.
- * @returns {void}
- */
-function mergeRuleConfigs(target, source) {
-    if (!isNonNullObject(source)) {
-        return;
-    }
-
-    for (const key of Object.keys(source)) {
-        if (key === "__proto__") {
-            continue;
-        }
-        const targetDef = target[key];
-        const sourceDef = source[key];
-
-        // Adopt the rule config which was found at first.
-        if (targetDef === void 0) {
-            if (Array.isArray(sourceDef)) {
-                target[key] = [...sourceDef];
-            } else {
-                target[key] = [sourceDef];
-            }
-
-        /*
-         * If the first found rule config is severity only and the current rule
-         * config has options, merge the severity and the options.
-         */
-        } else if (
-            targetDef.length === 1 &&
-            Array.isArray(sourceDef) &&
-            sourceDef.length >= 2
-        ) {
-            targetDef.push(...sourceDef.slice(1));
-        }
-    }
-}
-
-/**
- * Create the extracted config.
- * @param {ConfigArray} instance The config elements.
- * @param {number[]} indices The indices to use.
- * @returns {ExtractedConfig} The extracted config.
- */
-function createConfig(instance, indices) {
-    const config = new ExtractedConfig();
-    const ignorePatterns = [];
-
-    // Merge elements.
-    for (const index of indices) {
-        const element = instance[index];
-
-        // Adopt the parser which was found at first.
-        if (!config.parser && element.parser) {
-            if (element.parser.error) {
-                throw element.parser.error;
-            }
-            config.parser = element.parser;
-        }
-
-        // Adopt the processor which was found at first.
-        if (!config.processor && element.processor) {
-            config.processor = element.processor;
-        }
-
-        // Adopt the noInlineConfig which was found at first.
-        if (config.noInlineConfig === void 0 && element.noInlineConfig !== void 0) {
-            config.noInlineConfig = element.noInlineConfig;
-            config.configNameOfNoInlineConfig = element.name;
-        }
-
-        // Adopt the reportUnusedDisableDirectives which was found at first.
-        if (config.reportUnusedDisableDirectives === void 0 && element.reportUnusedDisableDirectives !== void 0) {
-            config.reportUnusedDisableDirectives = element.reportUnusedDisableDirectives;
-        }
-
-        // Collect ignorePatterns
-        if (element.ignorePattern) {
-            ignorePatterns.push(element.ignorePattern);
-        }
-
-        // Merge others.
-        mergeWithoutOverwrite(config.env, element.env);
-        mergeWithoutOverwrite(config.globals, element.globals);
-        mergeWithoutOverwrite(config.parserOptions, element.parserOptions);
-        mergeWithoutOverwrite(config.settings, element.settings);
-        mergePlugins(config.plugins, element.plugins);
-        mergeRuleConfigs(config.rules, element.rules);
-    }
-
-    // Create the predicate function for ignore patterns.
-    if (ignorePatterns.length > 0) {
-        config.ignores = IgnorePattern.createIgnore(ignorePatterns.reverse());
-    }
-
-    return config;
-}
-
-/**
- * Collect definitions.
- * @template T, U
- * @param {string} pluginId The plugin ID for prefix.
- * @param {Record<string,T>} defs The definitions to collect.
- * @param {Map<string, U>} map The map to output.
- * @param {function(T): U} [normalize] The normalize function for each value.
- * @returns {void}
- */
-function collect(pluginId, defs, map, normalize) {
-    if (defs) {
-        const prefix = pluginId && `${pluginId}/`;
-
-        for (const [key, value] of Object.entries(defs)) {
-            map.set(
-                `${prefix}${key}`,
-                normalize ? normalize(value) : value
-            );
-        }
-    }
-}
-
-/**
- * Normalize a rule definition.
- * @param {Function|Rule} rule The rule definition to normalize.
- * @returns {Rule} The normalized rule definition.
- */
-function normalizePluginRule(rule) {
-    return typeof rule === "function" ? { create: rule } : rule;
-}
-
-/**
- * Delete the mutation methods from a given map.
- * @param {Map<any, any>} map The map object to delete.
- * @returns {void}
- */
-function deleteMutationMethods(map) {
-    Object.defineProperties(map, {
-        clear: { configurable: true, value: void 0 },
-        delete: { configurable: true, value: void 0 },
-        set: { configurable: true, value: void 0 }
-    });
-}
-
-/**
- * Create `envMap`, `processorMap`, `ruleMap` with the plugins in the config array.
- * @param {ConfigArrayElement[]} elements The config elements.
- * @param {ConfigArrayInternalSlots} slots The internal slots.
- * @returns {void}
- */
-function initPluginMemberMaps(elements, slots) {
-    const processed = new Set();
-
-    slots.envMap = new Map();
-    slots.processorMap = new Map();
-    slots.ruleMap = new Map();
-
-    for (const element of elements) {
-        if (!element.plugins) {
-            continue;
-        }
-
-        for (const [pluginId, value] of Object.entries(element.plugins)) {
-            const plugin = value.definition;
-
-            if (!plugin || processed.has(pluginId)) {
-                continue;
-            }
-            processed.add(pluginId);
-
-            collect(pluginId, plugin.environments, slots.envMap);
-            collect(pluginId, plugin.processors, slots.processorMap);
-            collect(pluginId, plugin.rules, slots.ruleMap, normalizePluginRule);
-        }
-    }
-
-    deleteMutationMethods(slots.envMap);
-    deleteMutationMethods(slots.processorMap);
-    deleteMutationMethods(slots.ruleMap);
-}
-
-/**
- * Create `envMap`, `processorMap`, `ruleMap` with the plugins in the config array.
- * @param {ConfigArray} instance The config elements.
- * @returns {ConfigArrayInternalSlots} The extracted config.
- */
-function ensurePluginMemberMaps(instance) {
-    const slots = internalSlotsMap.get(instance);
-
-    if (!slots.ruleMap) {
-        initPluginMemberMaps(instance, slots);
-    }
-
-    return slots;
-}
-
-//------------------------------------------------------------------------------
-// Public Interface
-//------------------------------------------------------------------------------
-
-/**
- * The Config Array.
- *
- * `ConfigArray` instance contains all settings, parsers, and plugins.
- * You need to call `ConfigArray#extractConfig(filePath)` method in order to
- * extract, merge and get only the config data which is related to an arbitrary
- * file.
- * @extends {Array<ConfigArrayElement>}
- */
-class ConfigArray extends Array {
-
-    /**
-     * Get the plugin environments.
-     * The returned map cannot be mutated.
-     * @type {ReadonlyMap<string, Environment>} The plugin environments.
-     */
-    get pluginEnvironments() {
-        return ensurePluginMemberMaps(this).envMap;
-    }
-
-    /**
-     * Get the plugin processors.
-     * The returned map cannot be mutated.
-     * @type {ReadonlyMap<string, Processor>} The plugin processors.
-     */
-    get pluginProcessors() {
-        return ensurePluginMemberMaps(this).processorMap;
-    }
-
-    /**
-     * Get the plugin rules.
-     * The returned map cannot be mutated.
-     * @returns {ReadonlyMap<string, Rule>} The plugin rules.
-     */
-    get pluginRules() {
-        return ensurePluginMemberMaps(this).ruleMap;
-    }
-
-    /**
-     * Check if this config has `root` flag.
-     * @returns {boolean} `true` if this config array is root.
-     */
-    isRoot() {
-        for (let i = this.length - 1; i >= 0; --i) {
-            const root = this[i].root;
-
-            if (typeof root === "boolean") {
-                return root;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Extract the config data which is related to a given file.
-     * @param {string} filePath The absolute path to the target file.
-     * @returns {ExtractedConfig} The extracted config data.
-     */
-    extractConfig(filePath) {
-        const { cache } = internalSlotsMap.get(this);
-        const indices = getMatchedIndices(this, filePath);
-        const cacheKey = indices.join(",");
-
-        if (!cache.has(cacheKey)) {
-            cache.set(cacheKey, createConfig(this, indices));
-        }
-
-        return cache.get(cacheKey);
-    }
-
-    /**
-     * Check if a given path is an additional lint target.
-     * @param {string} filePath The absolute path to the target file.
-     * @returns {boolean} `true` if the file is an additional lint target.
-     */
-    isAdditionalTargetPath(filePath) {
-        for (const { criteria, type } of this) {
-            if (
-                type === "config" &&
-                criteria &&
-                !criteria.endsWithWildcard &&
-                criteria.test(filePath)
-            ) {
-                return true;
-            }
-        }
-        return false;
-    }
-}
-
-const exportObject = {
-    ConfigArray,
-
-    /**
-     * Get the used extracted configs.
-     * CLIEngine will use this method to collect used deprecated rules.
-     * @param {ConfigArray} instance The config array object to get.
-     * @returns {ExtractedConfig[]} The used extracted configs.
-     * @private
-     */
-    getUsedExtractedConfigs(instance) {
-        const { cache } = internalSlotsMap.get(instance);
-
-        return Array.from(cache.values());
-    }
-};
-
-module.exports = exportObject;
diff --git a/eslint/lib/cli-engine/config-array/config-dependency.js b/eslint/lib/cli-engine/config-array/config-dependency.js
deleted file mode 100644 (file)
index 0d5f6f7..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-/**
- * @fileoverview `ConfigDependency` class.
- *
- * `ConfigDependency` class expresses a loaded parser or plugin.
- *
- * If the parser or plugin was loaded successfully, it has `definition` property
- * and `filePath` property. Otherwise, it has `error` property.
- *
- * When `JSON.stringify()` converted a `ConfigDependency` object to a JSON, it
- * omits `definition` property.
- *
- * `ConfigArrayFactory` creates `ConfigDependency` objects when it loads parsers
- * or plugins.
- *
- * @author Toru Nagashima <https://github.com/mysticatea>
- */
-"use strict";
-
-const util = require("util");
-
-/**
- * The class is to store parsers or plugins.
- * This class hides the loaded object from `JSON.stringify()` and `console.log`.
- * @template T
- */
-class ConfigDependency {
-
-    /**
-     * Initialize this instance.
-     * @param {Object} data The dependency data.
-     * @param {T} [data.definition] The dependency if the loading succeeded.
-     * @param {Error} [data.error] The error object if the loading failed.
-     * @param {string} [data.filePath] The actual path to the dependency if the loading succeeded.
-     * @param {string} data.id The ID of this dependency.
-     * @param {string} data.importerName The name of the config file which loads this dependency.
-     * @param {string} data.importerPath The path to the config file which loads this dependency.
-     */
-    constructor({
-        definition = null,
-        error = null,
-        filePath = null,
-        id,
-        importerName,
-        importerPath
-    }) {
-
-        /**
-         * The loaded dependency if the loading succeeded.
-         * @type {T|null}
-         */
-        this.definition = definition;
-
-        /**
-         * The error object if the loading failed.
-         * @type {Error|null}
-         */
-        this.error = error;
-
-        /**
-         * The loaded dependency if the loading succeeded.
-         * @type {string|null}
-         */
-        this.filePath = filePath;
-
-        /**
-         * The ID of this dependency.
-         * @type {string}
-         */
-        this.id = id;
-
-        /**
-         * The name of the config file which loads this dependency.
-         * @type {string}
-         */
-        this.importerName = importerName;
-
-        /**
-         * The path to the config file which loads this dependency.
-         * @type {string}
-         */
-        this.importerPath = importerPath;
-    }
-
-    // eslint-disable-next-line jsdoc/require-description
-    /**
-     * @returns {Object} a JSON compatible object.
-     */
-    toJSON() {
-        const obj = this[util.inspect.custom]();
-
-        // Display `error.message` (`Error#message` is unenumerable).
-        if (obj.error instanceof Error) {
-            obj.error = { ...obj.error, message: obj.error.message };
-        }
-
-        return obj;
-    }
-
-    // eslint-disable-next-line jsdoc/require-description
-    /**
-     * @returns {Object} an object to display by `console.log()`.
-     */
-    [util.inspect.custom]() {
-        const {
-            definition: _ignore, // eslint-disable-line no-unused-vars
-            ...obj
-        } = this;
-
-        return obj;
-    }
-}
-
-/** @typedef {ConfigDependency<import("../../shared/types").Parser>} DependentParser */
-/** @typedef {ConfigDependency<import("../../shared/types").Plugin>} DependentPlugin */
-
-module.exports = { ConfigDependency };
diff --git a/eslint/lib/cli-engine/config-array/extracted-config.js b/eslint/lib/cli-engine/config-array/extracted-config.js
deleted file mode 100644 (file)
index b27d6ff..0000000
+++ /dev/null
@@ -1,146 +0,0 @@
-/**
- * @fileoverview `ExtractedConfig` class.
- *
- * `ExtractedConfig` class expresses a final configuration for a specific file.
- *
- * It provides one method.
- *
- * - `toCompatibleObjectAsConfigFileContent()`
- *      Convert this configuration to the compatible object as the content of
- *      config files. It converts the loaded parser and plugins to strings.
- *      `CLIEngine#getConfigForFile(filePath)` method uses this method.
- *
- * `ConfigArray#extractConfig(filePath)` creates a `ExtractedConfig` instance.
- *
- * @author Toru Nagashima <https://github.com/mysticatea>
- */
-"use strict";
-
-const { IgnorePattern } = require("./ignore-pattern");
-
-// For VSCode intellisense
-/** @typedef {import("../../shared/types").ConfigData} ConfigData */
-/** @typedef {import("../../shared/types").GlobalConf} GlobalConf */
-/** @typedef {import("../../shared/types").SeverityConf} SeverityConf */
-/** @typedef {import("./config-dependency").DependentParser} DependentParser */
-/** @typedef {import("./config-dependency").DependentPlugin} DependentPlugin */
-
-/**
- * Check if `xs` starts with `ys`.
- * @template T
- * @param {T[]} xs The array to check.
- * @param {T[]} ys The array that may be the first part of `xs`.
- * @returns {boolean} `true` if `xs` starts with `ys`.
- */
-function startsWith(xs, ys) {
-    return xs.length >= ys.length && ys.every((y, i) => y === xs[i]);
-}
-
-/**
- * The class for extracted config data.
- */
-class ExtractedConfig {
-    constructor() {
-
-        /**
-         * The config name what `noInlineConfig` setting came from.
-         * @type {string}
-         */
-        this.configNameOfNoInlineConfig = "";
-
-        /**
-         * Environments.
-         * @type {Record<string, boolean>}
-         */
-        this.env = {};
-
-        /**
-         * Global variables.
-         * @type {Record<string, GlobalConf>}
-         */
-        this.globals = {};
-
-        /**
-         * The glob patterns that ignore to lint.
-         * @type {(((filePath:string, dot?:boolean) => boolean) & { basePath:string; patterns:string[] }) | undefined}
-         */
-        this.ignores = void 0;
-
-        /**
-         * The flag that disables directive comments.
-         * @type {boolean|undefined}
-         */
-        this.noInlineConfig = void 0;
-
-        /**
-         * Parser definition.
-         * @type {DependentParser|null}
-         */
-        this.parser = null;
-
-        /**
-         * Options for the parser.
-         * @type {Object}
-         */
-        this.parserOptions = {};
-
-        /**
-         * Plugin definitions.
-         * @type {Record<string, DependentPlugin>}
-         */
-        this.plugins = {};
-
-        /**
-         * Processor ID.
-         * @type {string|null}
-         */
-        this.processor = null;
-
-        /**
-         * The flag that reports unused `eslint-disable` directive comments.
-         * @type {boolean|undefined}
-         */
-        this.reportUnusedDisableDirectives = void 0;
-
-        /**
-         * Rule settings.
-         * @type {Record<string, [SeverityConf, ...any[]]>}
-         */
-        this.rules = {};
-
-        /**
-         * Shared settings.
-         * @type {Object}
-         */
-        this.settings = {};
-    }
-
-    /**
-     * Convert this config to the compatible object as a config file content.
-     * @returns {ConfigData} The converted object.
-     */
-    toCompatibleObjectAsConfigFileContent() {
-        const {
-            /* eslint-disable no-unused-vars */
-            configNameOfNoInlineConfig: _ignore1,
-            processor: _ignore2,
-            /* eslint-enable no-unused-vars */
-            ignores,
-            ...config
-        } = this;
-
-        config.parser = config.parser && config.parser.filePath;
-        config.plugins = Object.keys(config.plugins).filter(Boolean).reverse();
-        config.ignorePatterns = ignores ? ignores.patterns : [];
-
-        // Strip the default patterns from `ignorePatterns`.
-        if (startsWith(config.ignorePatterns, IgnorePattern.DefaultPatterns)) {
-            config.ignorePatterns =
-                config.ignorePatterns.slice(IgnorePattern.DefaultPatterns.length);
-        }
-
-        return config;
-    }
-}
-
-module.exports = { ExtractedConfig };
diff --git a/eslint/lib/cli-engine/config-array/ignore-pattern.js b/eslint/lib/cli-engine/config-array/ignore-pattern.js
deleted file mode 100644 (file)
index 6eaec42..0000000
+++ /dev/null
@@ -1,237 +0,0 @@
-/**
- * @fileoverview `IgnorePattern` class.
- *
- * `IgnorePattern` class has the set of glob patterns and the base path.
- *
- * It provides two static methods.
- *
- * - `IgnorePattern.createDefaultIgnore(cwd)`
- *      Create the default predicate function.
- * - `IgnorePattern.createIgnore(ignorePatterns)`
- *      Create the predicate function from multiple `IgnorePattern` objects.
- *
- * It provides two properties and a method.
- *
- * - `patterns`
- *      The glob patterns that ignore to lint.
- * - `basePath`
- *      The base path of the glob patterns. If absolute paths existed in the
- *      glob patterns, those are handled as relative paths to the base path.
- * - `getPatternsRelativeTo(basePath)`
- *      Get `patterns` as modified for a given base path. It modifies the
- *      absolute paths in the patterns as prepending the difference of two base
- *      paths.
- *
- * `ConfigArrayFactory` creates `IgnorePattern` objects when it processes
- * `ignorePatterns` properties.
- *
- * @author Toru Nagashima <https://github.com/mysticatea>
- */
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const assert = require("assert");
-const path = require("path");
-const ignore = require("ignore");
-const debug = require("debug")("eslint:ignore-pattern");
-
-/** @typedef {ReturnType<import("ignore").default>} Ignore */
-
-//------------------------------------------------------------------------------
-// Helpers
-//------------------------------------------------------------------------------
-
-/**
- * Get the path to the common ancestor directory of given paths.
- * @param {string[]} sourcePaths The paths to calculate the common ancestor.
- * @returns {string} The path to the common ancestor directory.
- */
-function getCommonAncestorPath(sourcePaths) {
-    let result = sourcePaths[0];
-
-    for (let i = 1; i < sourcePaths.length; ++i) {
-        const a = result;
-        const b = sourcePaths[i];
-
-        // Set the shorter one (it's the common ancestor if one includes the other).
-        result = a.length < b.length ? a : b;
-
-        // Set the common ancestor.
-        for (let j = 0, lastSepPos = 0; j < a.length && j < b.length; ++j) {
-            if (a[j] !== b[j]) {
-                result = a.slice(0, lastSepPos);
-                break;
-            }
-            if (a[j] === path.sep) {
-                lastSepPos = j;
-            }
-        }
-    }
-
-    let resolvedResult = result || path.sep;
-
-    // if Windows common ancestor is root of drive must have trailing slash to be absolute.
-    if (resolvedResult && resolvedResult.endsWith(":") && process.platform === "win32") {
-        resolvedResult += path.sep;
-    }
-    return resolvedResult;
-}
-
-/**
- * Make relative path.
- * @param {string} from The source path to get relative path.
- * @param {string} to The destination path to get relative path.
- * @returns {string} The relative path.
- */
-function relative(from, to) {
-    const relPath = path.relative(from, to);
-
-    if (path.sep === "/") {
-        return relPath;
-    }
-    return relPath.split(path.sep).join("/");
-}
-
-/**
- * Get the trailing slash if existed.
- * @param {string} filePath The path to check.
- * @returns {string} The trailing slash if existed.
- */
-function dirSuffix(filePath) {
-    const isDir = (
-        filePath.endsWith(path.sep) ||
-        (process.platform === "win32" && filePath.endsWith("/"))
-    );
-
-    return isDir ? "/" : "";
-}
-
-const DefaultPatterns = Object.freeze(["/**/node_modules/*"]);
-const DotPatterns = Object.freeze([".*", "!.eslintrc.*", "!../"]);
-
-//------------------------------------------------------------------------------
-// Public
-//------------------------------------------------------------------------------
-
-class IgnorePattern {
-
-    /**
-     * The default patterns.
-     * @type {string[]}
-     */
-    static get DefaultPatterns() {
-        return DefaultPatterns;
-    }
-
-    /**
-     * Create the default predicate function.
-     * @param {string} cwd The current working directory.
-     * @returns {((filePath:string, dot:boolean) => boolean) & {basePath:string; patterns:string[]}}
-     * The preficate function.
-     * The first argument is an absolute path that is checked.
-     * The second argument is the flag to not ignore dotfiles.
-     * If the predicate function returned `true`, it means the path should be ignored.
-     */
-    static createDefaultIgnore(cwd) {
-        return this.createIgnore([new IgnorePattern(DefaultPatterns, cwd)]);
-    }
-
-    /**
-     * Create the predicate function from multiple `IgnorePattern` objects.
-     * @param {IgnorePattern[]} ignorePatterns The list of ignore patterns.
-     * @returns {((filePath:string, dot?:boolean) => boolean) & {basePath:string; patterns:string[]}}
-     * The preficate function.
-     * The first argument is an absolute path that is checked.
-     * The second argument is the flag to not ignore dotfiles.
-     * If the predicate function returned `true`, it means the path should be ignored.
-     */
-    static createIgnore(ignorePatterns) {
-        debug("Create with: %o", ignorePatterns);
-
-        const basePath = getCommonAncestorPath(ignorePatterns.map(p => p.basePath));
-        const patterns = [].concat(
-            ...ignorePatterns.map(p => p.getPatternsRelativeTo(basePath))
-        );
-        const ig = ignore().add([...DotPatterns, ...patterns]);
-        const dotIg = ignore().add(patterns);
-
-        debug("  processed: %o", { basePath, patterns });
-
-        return Object.assign(
-            (filePath, dot = false) => {
-                assert(path.isAbsolute(filePath), "'filePath' should be an absolute path.");
-                const relPathRaw = relative(basePath, filePath);
-                const relPath = relPathRaw && (relPathRaw + dirSuffix(filePath));
-                const adoptedIg = dot ? dotIg : ig;
-                const result = relPath !== "" && adoptedIg.ignores(relPath);
-
-                debug("Check", { filePath, dot, relativePath: relPath, result });
-                return result;
-            },
-            { basePath, patterns }
-        );
-    }
-
-    /**
-     * Initialize a new `IgnorePattern` instance.
-     * @param {string[]} patterns The glob patterns that ignore to lint.
-     * @param {string} basePath The base path of `patterns`.
-     */
-    constructor(patterns, basePath) {
-        assert(path.isAbsolute(basePath), "'basePath' should be an absolute path.");
-
-        /**
-         * The glob patterns that ignore to lint.
-         * @type {string[]}
-         */
-        this.patterns = patterns;
-
-        /**
-         * The base path of `patterns`.
-         * @type {string}
-         */
-        this.basePath = basePath;
-
-        /**
-         * If `true` then patterns which don't start with `/` will match the paths to the outside of `basePath`. Defaults to `false`.
-         *
-         * It's set `true` for `.eslintignore`, `package.json`, and `--ignore-path` for backward compatibility.
-         * It's `false` as-is for `ignorePatterns` property in config files.
-         * @type {boolean}
-         */
-        this.loose = false;
-    }
-
-    /**
-     * Get `patterns` as modified for a given base path. It modifies the
-     * absolute paths in the patterns as prepending the difference of two base
-     * paths.
-     * @param {string} newBasePath The base path.
-     * @returns {string[]} Modifired patterns.
-     */
-    getPatternsRelativeTo(newBasePath) {
-        assert(path.isAbsolute(newBasePath), "'newBasePath' should be an absolute path.");
-        const { basePath, loose, patterns } = this;
-
-        if (newBasePath === basePath) {
-            return patterns;
-        }
-        const prefix = `/${relative(newBasePath, basePath)}`;
-
-        return patterns.map(pattern => {
-            const negative = pattern.startsWith("!");
-            const head = negative ? "!" : "";
-            const body = negative ? pattern.slice(1) : pattern;
-
-            if (body.startsWith("/") || body.startsWith("../")) {
-                return `${head}${prefix}${body}`;
-            }
-            return loose ? pattern : `${head}${prefix}/**/${body}`;
-        });
-    }
-}
-
-module.exports = { IgnorePattern };
diff --git a/eslint/lib/cli-engine/config-array/index.js b/eslint/lib/cli-engine/config-array/index.js
deleted file mode 100644 (file)
index 928d76c..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * @fileoverview `ConfigArray` class.
- * @author Toru Nagashima <https://github.com/mysticatea>
- */
-"use strict";
-
-const { ConfigArray, getUsedExtractedConfigs } = require("./config-array");
-const { ConfigDependency } = require("./config-dependency");
-const { ExtractedConfig } = require("./extracted-config");
-const { IgnorePattern } = require("./ignore-pattern");
-const { OverrideTester } = require("./override-tester");
-
-module.exports = {
-    ConfigArray,
-    ConfigDependency,
-    ExtractedConfig,
-    IgnorePattern,
-    OverrideTester,
-    getUsedExtractedConfigs
-};
diff --git a/eslint/lib/cli-engine/config-array/override-tester.js b/eslint/lib/cli-engine/config-array/override-tester.js
deleted file mode 100644 (file)
index e7ba120..0000000
+++ /dev/null
@@ -1,223 +0,0 @@
-/**
- * @fileoverview `OverrideTester` class.
- *
- * `OverrideTester` class handles `files` property and `excludedFiles` property
- * of `overrides` config.
- *
- * It provides one method.
- *
- * - `test(filePath)`
- *      Test if a file path matches the pair of `files` property and
- *      `excludedFiles` property. The `filePath` argument must be an absolute
- *      path.
- *
- * `ConfigArrayFactory` creates `OverrideTester` objects when it processes
- * `overrides` properties.
- *
- * @author Toru Nagashima <https://github.com/mysticatea>
- */
-"use strict";
-
-const assert = require("assert");
-const path = require("path");
-const util = require("util");
-const { Minimatch } = require("minimatch");
-const minimatchOpts = { dot: true, matchBase: true };
-
-/**
- * @typedef {Object} Pattern
- * @property {InstanceType<Minimatch>[] | null} includes The positive matchers.
- * @property {InstanceType<Minimatch>[] | null} excludes The negative matchers.
- */
-
-/**
- * Normalize a given pattern to an array.
- * @param {string|string[]|undefined} patterns A glob pattern or an array of glob patterns.
- * @returns {string[]|null} Normalized patterns.
- * @private
- */
-function normalizePatterns(patterns) {
-    if (Array.isArray(patterns)) {
-        return patterns.filter(Boolean);
-    }
-    if (typeof patterns === "string" && patterns) {
-        return [patterns];
-    }
-    return [];
-}
-
-/**
- * Create the matchers of given patterns.
- * @param {string[]} patterns The patterns.
- * @returns {InstanceType<Minimatch>[] | null} The matchers.
- */
-function toMatcher(patterns) {
-    if (patterns.length === 0) {
-        return null;
-    }
-    return patterns.map(pattern => {
-        if (/^\.[/\\]/u.test(pattern)) {
-            return new Minimatch(
-                pattern.slice(2),
-
-                // `./*.js` should not match with `subdir/foo.js`
-                { ...minimatchOpts, matchBase: false }
-            );
-        }
-        return new Minimatch(pattern, minimatchOpts);
-    });
-}
-
-/**
- * Convert a given matcher to string.
- * @param {Pattern} matchers The matchers.
- * @returns {string} The string expression of the matcher.
- */
-function patternToJson({ includes, excludes }) {
-    return {
-        includes: includes && includes.map(m => m.pattern),
-        excludes: excludes && excludes.map(m => m.pattern)
-    };
-}
-
-/**
- * The class to test given paths are matched by the patterns.
- */
-class OverrideTester {
-
-    /**
-     * Create a tester with given criteria.
-     * If there are no criteria, returns `null`.
-     * @param {string|string[]} files The glob patterns for included files.
-     * @param {string|string[]} excludedFiles The glob patterns for excluded files.
-     * @param {string} basePath The path to the base directory to test paths.
-     * @returns {OverrideTester|null} The created instance or `null`.
-     */
-    static create(files, excludedFiles, basePath) {
-        const includePatterns = normalizePatterns(files);
-        const excludePatterns = normalizePatterns(excludedFiles);
-        let endsWithWildcard = false;
-
-        if (includePatterns.length === 0) {
-            return null;
-        }
-
-        // Rejects absolute paths or relative paths to parents.
-        for (const pattern of includePatterns) {
-            if (path.isAbsolute(pattern) || pattern.includes("..")) {
-                throw new Error(`Invalid override pattern (expected relative path not containing '..'): ${pattern}`);
-            }
-            if (pattern.endsWith("*")) {
-                endsWithWildcard = true;
-            }
-        }
-        for (const pattern of excludePatterns) {
-            if (path.isAbsolute(pattern) || pattern.includes("..")) {
-                throw new Error(`Invalid override pattern (expected relative path not containing '..'): ${pattern}`);
-            }
-        }
-
-        const includes = toMatcher(includePatterns);
-        const excludes = toMatcher(excludePatterns);
-
-        return new OverrideTester(
-            [{ includes, excludes }],
-            basePath,
-            endsWithWildcard
-        );
-    }
-
-    /**
-     * Combine two testers by logical and.
-     * If either of the testers was `null`, returns the other tester.
-     * The `basePath` property of the two must be the same value.
-     * @param {OverrideTester|null} a A tester.
-     * @param {OverrideTester|null} b Another tester.
-     * @returns {OverrideTester|null} Combined tester.
-     */
-    static and(a, b) {
-        if (!b) {
-            return a && new OverrideTester(
-                a.patterns,
-                a.basePath,
-                a.endsWithWildcard
-            );
-        }
-        if (!a) {
-            return new OverrideTester(
-                b.patterns,
-                b.basePath,
-                b.endsWithWildcard
-            );
-        }
-
-        assert.strictEqual(a.basePath, b.basePath);
-        return new OverrideTester(
-            a.patterns.concat(b.patterns),
-            a.basePath,
-            a.endsWithWildcard || b.endsWithWildcard
-        );
-    }
-
-    /**
-     * Initialize this instance.
-     * @param {Pattern[]} patterns The matchers.
-     * @param {string} basePath The base path.
-     * @param {boolean} endsWithWildcard If `true` then a pattern ends with `*`.
-     */
-    constructor(patterns, basePath, endsWithWildcard = false) {
-
-        /** @type {Pattern[]} */
-        this.patterns = patterns;
-
-        /** @type {string} */
-        this.basePath = basePath;
-
-        /** @type {boolean} */
-        this.endsWithWildcard = endsWithWildcard;
-    }
-
-    /**
-     * Test if a given path is matched or not.
-     * @param {string} filePath The absolute path to the target file.
-     * @returns {boolean} `true` if the path was matched.
-     */
-    test(filePath) {
-        if (typeof filePath !== "string" || !path.isAbsolute(filePath)) {
-            throw new Error(`'filePath' should be an absolute path, but got ${filePath}.`);
-        }
-        const relativePath = path.relative(this.basePath, filePath);
-
-        return this.patterns.every(({ includes, excludes }) => (
-            (!includes || includes.some(m => m.match(relativePath))) &&
-            (!excludes || !excludes.some(m => m.match(relativePath)))
-        ));
-    }
-
-    // eslint-disable-next-line jsdoc/require-description
-    /**
-     * @returns {Object} a JSON compatible object.
-     */
-    toJSON() {
-        if (this.patterns.length === 1) {
-            return {
-                ...patternToJson(this.patterns[0]),
-                basePath: this.basePath
-            };
-        }
-        return {
-            AND: this.patterns.map(patternToJson),
-            basePath: this.basePath
-        };
-    }
-
-    // eslint-disable-next-line jsdoc/require-description
-    /**
-     * @returns {Object} an object to display by `console.log()`.
-     */
-    [util.inspect.custom]() {
-        return this.toJSON();
-    }
-}
-
-module.exports = { OverrideTester };
index 7c433d32f44b1cbd394a29eb0a30260956ec994b..c2961d71ac06327cf283aa5e7e9555d9357f2add 100644 (file)
@@ -40,8 +40,13 @@ const getGlobParent = require("glob-parent");
 const isGlob = require("is-glob");
 const { escapeRegExp } = require("lodash");
 const { Minimatch } = require("minimatch");
-const { IgnorePattern } = require("./config-array");
-const { CascadingConfigArrayFactory } = require("./cascading-config-array-factory");
+
+const {
+    Legacy: {
+        IgnorePattern,
+        CascadingConfigArrayFactory
+    }
+} = require("@eslint/eslintrc");
 const debug = require("debug")("eslint:file-enumerator");
 
 //------------------------------------------------------------------------------
@@ -208,7 +213,11 @@ class FileEnumerator {
      */
     constructor({
         cwd = process.cwd(),
-        configArrayFactory = new CascadingConfigArrayFactory({ cwd }),
+        configArrayFactory = new CascadingConfigArrayFactory({
+            cwd,
+            eslintRecommendedPath: path.resolve(__dirname, "../../conf/eslint-recommended.js"),
+            eslintAllPath: path.resolve(__dirname, "../../conf/eslint-all.js")
+        }),
         extensions = null,
         globInputPaths = true,
         errorOnUnmatchedPattern = true,
index ba4d1b5b3ec5324c73b6bc5528ec034ae1ac2cb5..f19b6fc0957e5dd7d90731a3e9b0c75283a5514c 100644 (file)
@@ -42,8 +42,8 @@ module.exports = function(results) {
 
         messages.forEach(message => {
             output += [
-                `<error line="${xmlEscape(message.line)}"`,
-                `column="${xmlEscape(message.column)}"`,
+                `<error line="${xmlEscape(message.line || 0)}"`,
+                `column="${xmlEscape(message.column || 0)}"`,
                 `severity="${xmlEscape(getMessageType(message))}"`,
                 `message="${xmlEscape(message.message)}${message.ruleId ? ` (${message.ruleId})` : ""}"`,
                 `source="${message.ruleId ? xmlEscape(`eslint.rules.${message.ruleId}`) : ""}" />`
index d195aab09f1918256e744832361d7f541620678a..a51ffbfe41a63716fd9ead8336c8b13c0eb962af 100644 (file)
@@ -15,7 +15,13 @@ const fs = require("fs");
 const { promisify } = require("util");
 const { CLIEngine, getCLIEngineInternalSlots } = require("../cli-engine/cli-engine");
 const BuiltinRules = require("../rules");
-const { getRuleSeverity } = require("../shared/config-ops");
+const {
+    Legacy: {
+        ConfigOps: {
+            getRuleSeverity
+        }
+    }
+} = require("@eslint/eslintrc");
 const { version } = require("../../package.json");
 
 //------------------------------------------------------------------------------
index 2b0aa12ac13df610b5cf352217162937e9e35f75..0ace177aa16a425049e99679529d12baf76f8f73 100644 (file)
@@ -11,7 +11,7 @@
 
 const lodash = require("lodash"),
     recConfig = require("../../conf/eslint-recommended"),
-    ConfigOps = require("../shared/config-ops"),
+    ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"),
     { Linter } = require("../linter"),
     configRule = require("./config-rule");
 
index dce39946e6b876d648f5dc8df3e6d15d151aea33..f7d4cc7a171fe9828a55fd02ecc85ba3787aacb5 100644 (file)
 
 const util = require("util"),
     path = require("path"),
-    inquirer = require("inquirer"),
+    enquirer = require("enquirer"),
     ProgressBar = require("progress"),
     semver = require("semver"),
     espree = require("espree"),
     recConfig = require("../../conf/eslint-recommended"),
-    ConfigOps = require("../shared/config-ops"),
+    ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"),
     log = require("../shared/logging"),
-    naming = require("../shared/naming"),
+    naming = require("@eslint/eslintrc/lib/shared/naming"),
     ModuleResolver = require("../shared/relative-module-resolver"),
     autoconfig = require("./autoconfig.js"),
     ConfigFile = require("./config-file"),
@@ -146,7 +146,7 @@ function getModulesList(config, installESLint) {
  *
  * Note: This clones the config object and returns a new config to avoid mutating
  * the original config parameter.
- * @param   {Object} answers  answers received from inquirer
+ * @param   {Object} answers  answers received from enquirer
  * @param   {Object} config   config object
  * @returns {Object}          config object with configured rules
  */
@@ -253,7 +253,7 @@ function configureRules(answers, config) {
 
 /**
  * process user's answers and create config object
- * @param {Object} answers answers received from inquirer
+ * @param {Object} answers answers received from enquirer
  * @returns {Object} config object
  */
 function processAnswers(answers) {
@@ -265,7 +265,7 @@ function processAnswers(answers) {
     };
 
     config.parserOptions.ecmaVersion = espree.latestEcmaVersion;
-    config.env.es2020 = true;
+    config.env.es2021 = true;
 
     // set the module type
     if (answers.moduleType === "esm") {
@@ -321,7 +321,6 @@ function processAnswers(answers) {
         }
     }
     if (answers.typescript && config.extends.includes("eslint:recommended")) {
-        config.extends.push("plugin:@typescript-eslint/eslint-recommended");
         config.extends.push("plugin:@typescript-eslint/recommended");
     }
 
@@ -409,7 +408,7 @@ function installModules(modules) {
     npmUtils.installSyncSaveDev(modules);
 }
 
-/* istanbul ignore next: no need to test inquirer */
+/* istanbul ignore next: no need to test enquirer */
 /**
  * Ask user to install modules.
  * @param   {string[]} modules Array of modules to be installed.
@@ -425,14 +424,19 @@ function askInstallModules(modules, packageJsonExists) {
 
     log.info("The config that you've selected requires the following dependencies:\n");
     log.info(modules.join(" "));
-    return inquirer.prompt([
+    return enquirer.prompt([
         {
-            type: "confirm",
+            type: "toggle",
             name: "executeInstallation",
             message: "Would you like to install them now with npm?",
-            default: true,
-            when() {
-                return modules.length && packageJsonExists;
+            enabled: "Yes",
+            disabled: "No",
+            initial: 1,
+            skip() {
+                return !(modules.length && packageJsonExists);
+            },
+            result(input) {
+                return this.skipped ? null : input;
             }
         }
     ]).then(({ executeInstallation }) => {
@@ -442,114 +446,124 @@ function askInstallModules(modules, packageJsonExists) {
     });
 }
 
-/* istanbul ignore next: no need to test inquirer */
+/* istanbul ignore next: no need to test enquirer */
 /**
  * Ask use a few questions on command prompt
  * @returns {Promise} The promise with the result of the prompt
  */
 function promptUser() {
 
-    return inquirer.prompt([
+    return enquirer.prompt([
         {
-            type: "list",
+            type: "select",
             name: "purpose",
             message: "How would you like to use ESLint?",
-            default: "problems",
+
+            // The returned number matches the name value of nth in the choices array.
+            initial: 1,
             choices: [
-                { name: "To check syntax only", value: "syntax" },
-                { name: "To check syntax and find problems", value: "problems" },
-                { name: "To check syntax, find problems, and enforce code style", value: "style" }
+                { message: "To check syntax only", name: "syntax" },
+                { message: "To check syntax and find problems", name: "problems" },
+                { message: "To check syntax, find problems, and enforce code style", name: "style" }
             ]
         },
         {
-            type: "list",
+            type: "select",
             name: "moduleType",
             message: "What type of modules does your project use?",
-            default: "esm",
+            initial: 0,
             choices: [
-                { name: "JavaScript modules (import/export)", value: "esm" },
-                { name: "CommonJS (require/exports)", value: "commonjs" },
-                { name: "None of these", value: "none" }
+                { message: "JavaScript modules (import/export)", name: "esm" },
+                { message: "CommonJS (require/exports)", name: "commonjs" },
+                { message: "None of these", name: "none" }
             ]
         },
         {
-            type: "list",
+            type: "select",
             name: "framework",
             message: "Which framework does your project use?",
-            default: "react",
+            initial: 0,
             choices: [
-                { name: "React", value: "react" },
-                { name: "Vue.js", value: "vue" },
-                { name: "None of these", value: "none" }
+                { message: "React", name: "react" },
+                { message: "Vue.js", name: "vue" },
+                { message: "None of these", name: "none" }
             ]
         },
         {
-            type: "confirm",
+            type: "toggle",
             name: "typescript",
             message: "Does your project use TypeScript?",
-            default: false
+            enabled: "Yes",
+            disabled: "No",
+            initial: 0
         },
         {
-            type: "checkbox",
+            type: "multiselect",
             name: "env",
             message: "Where does your code run?",
-            default: ["browser"],
+            hint: "(Press <space> to select, <a> to toggle all, <i> to invert selection)",
+            initial: 0,
             choices: [
-                { name: "Browser", value: "browser" },
-                { name: "Node", value: "node" }
+                { message: "Browser", name: "browser" },
+                { message: "Node", name: "node" }
             ]
         },
         {
-            type: "list",
+            type: "select",
             name: "source",
             message: "How would you like to define a style for your project?",
-            default: "guide",
             choices: [
-                { name: "Use a popular style guide", value: "guide" },
-                { name: "Answer questions about your style", value: "prompt" },
-                { name: "Inspect your JavaScript file(s)", value: "auto" }
+                { message: "Use a popular style guide", name: "guide" },
+                { message: "Answer questions about your style", name: "prompt" },
+                { message: "Inspect your JavaScript file(s)", name: "auto" }
             ],
-            when(answers) {
-                return answers.purpose === "style";
+            skip() {
+                return this.state.answers.purpose !== "style";
+            },
+            result(input) {
+                return this.skipped ? null : input;
             }
         },
         {
-            type: "list",
+            type: "select",
             name: "styleguide",
             message: "Which style guide do you want to follow?",
             choices: [
-                { name: "Airbnb: https://github.com/airbnb/javascript", value: "airbnb" },
-                { name: "Standard: https://github.com/standard/standard", value: "standard" },
-                { name: "Google: https://github.com/google/eslint-config-google", value: "google" }
+                { 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" }
             ],
-            when(answers) {
-                answers.packageJsonExists = npmUtils.checkPackageJson();
-                return answers.source === "guide" && answers.packageJsonExists;
+            skip() {
+                this.state.answers.packageJsonExists = npmUtils.checkPackageJson();
+                return !(this.state.answers.source === "guide" && this.state.answers.packageJsonExists);
+            },
+            result(input) {
+                return this.skipped ? null : input;
             }
         },
         {
             type: "input",
             name: "patterns",
             message: "Which file(s), path(s), or glob(s) should be examined?",
-            when(answers) {
-                return (answers.source === "auto");
+            skip() {
+                return this.state.answers.source !== "auto";
             },
             validate(input) {
-                if (input.trim().length === 0 && input.trim() !== ",") {
+                if (!this.skipped && input.trim().length === 0 && input.trim() !== ",") {
                     return "You must tell us what code to examine. Try again.";
                 }
                 return true;
             }
         },
         {
-            type: "list",
+            type: "select",
             name: "format",
             message: "What format do you want your config file to be in?",
-            default: "JavaScript",
+            initial: 0,
             choices: ["JavaScript", "YAML", "JSON"]
         },
         {
-            type: "confirm",
+            type: "toggle",
             name: "installESLint",
             message(answers) {
                 const verb = semver.ltr(answers.localESLintVersion, answers.requiredESLintVersionRange)
@@ -558,9 +572,14 @@ function promptUser() {
 
                 return `The style guide "${answers.styleguide}" requires eslint@${answers.requiredESLintVersionRange}. You are currently using eslint@${answers.localESLintVersion}.\n  Do you want to ${verb}?`;
             },
-            default: true,
-            when(answers) {
-                return answers.source === "guide" && answers.packageJsonExists && hasESLintVersionConflict(answers);
+            enabled: "Yes",
+            disabled: "No",
+            initial: 1,
+            skip() {
+                return !(this.state.answers.source === "guide" && this.state.answers.packageJsonExists && hasESLintVersionConflict(this.state.answers));
+            },
+            result(input) {
+                return this.skipped ? null : input;
             }
         }
     ]).then(earlyAnswers => {
@@ -613,33 +632,35 @@ function promptUser() {
         }
 
         // continue with the style questions otherwise...
-        return inquirer.prompt([
+        return enquirer.prompt([
             {
-                type: "list",
+                type: "select",
                 name: "indent",
                 message: "What style of indentation do you use?",
-                default: "tab",
-                choices: [{ name: "Tabs", value: "tab" }, { name: "Spaces", value: 4 }]
+                initial: 0,
+                choices: [{ message: "Tabs", name: "tab" }, { message: "Spaces", name: 4 }]
             },
             {
-                type: "list",
+                type: "select",
                 name: "quotes",
                 message: "What quotes do you use for strings?",
-                default: "double",
-                choices: [{ name: "Double", value: "double" }, { name: "Single", value: "single" }]
+                initial: 0,
+                choices: [{ message: "Double", name: "double" }, { message: "Single", name: "single" }]
             },
             {
-                type: "list",
+                type: "select",
                 name: "linebreak",
                 message: "What line endings do you use?",
-                default: "unix",
-                choices: [{ name: "Unix", value: "unix" }, { name: "Windows", value: "windows" }]
+                initial: 0,
+                choices: [{ message: "Unix", name: "unix" }, { message: "Windows", name: "windows" }]
             },
             {
-                type: "confirm",
+                type: "toggle",
                 name: "semi",
                 message: "Do you require semicolons?",
-                default: true
+                enabled: "Yes",
+                disabled: "No",
+                initial: 1
             }
         ]).then(answers => {
             const totalAnswers = Object.assign({}, earlyAnswers, answers);
index b612cf43566b0ce0618720a64aa1cbeec2b5ebed..47427c11a3d2500d458ddeaf0830a8d9c62b64b8 100644 (file)
@@ -39,6 +39,17 @@ function isHandledLogicalOperator(operator) {
     return operator === "&&" || operator === "||" || operator === "??";
 }
 
+/**
+ * Checks whether the given assignment operator is a logical assignment operator.
+ * Logical assignments are taken into account for the code path analysis
+ * because of their short-circuiting semantics.
+ * @param {string} operator The operator found in the AssignmentExpression node
+ * @returns {boolean} `true` if the operator is "&&=" or "||=" or "??="
+ */
+function isLogicalAssignmentOperator(operator) {
+    return operator === "&&=" || operator === "||=" || operator === "??=";
+}
+
 /**
  * Gets the label if the parent node of a given node is a LabeledStatement.
  * @param {ASTNode} node A node to get.
@@ -71,6 +82,9 @@ function isForkingByTrueOrFalse(node) {
         case "LogicalExpression":
             return isHandledLogicalOperator(parent.operator);
 
+        case "AssignmentExpression":
+            return isLogicalAssignmentOperator(parent.operator);
+
         default:
             return false;
     }
@@ -244,6 +258,19 @@ function preprocess(analyzer, node) {
     const parent = node.parent;
 
     switch (parent.type) {
+
+        // The `arguments.length == 0` case is in `postprocess` function.
+        case "CallExpression":
+            if (parent.optional === true && parent.arguments.length >= 1 && parent.arguments[0] === node) {
+                state.makeOptionalRight();
+            }
+            break;
+        case "MemberExpression":
+            if (parent.optional === true && parent.property === node) {
+                state.makeOptionalRight();
+            }
+            break;
+
         case "LogicalExpression":
             if (
                 parent.right === node &&
@@ -253,6 +280,15 @@ function preprocess(analyzer, node) {
             }
             break;
 
+        case "AssignmentExpression":
+            if (
+                parent.right === node &&
+                isLogicalAssignmentOperator(parent.operator)
+            ) {
+                state.makeLogicalRight();
+            }
+            break;
+
         case "ConditionalExpression":
         case "IfStatement":
 
@@ -377,6 +413,20 @@ function processCodePathToEnter(analyzer, node) {
             analyzer.emitter.emit("onCodePathStart", codePath, node);
             break;
 
+        case "ChainExpression":
+            state.pushChainContext();
+            break;
+        case "CallExpression":
+            if (node.optional === true) {
+                state.makeOptionalNode();
+            }
+            break;
+        case "MemberExpression":
+            if (node.optional === true) {
+                state.makeOptionalNode();
+            }
+            break;
+
         case "LogicalExpression":
             if (isHandledLogicalOperator(node.operator)) {
                 state.pushChoiceContext(
@@ -386,6 +436,15 @@ function processCodePathToEnter(analyzer, node) {
             }
             break;
 
+        case "AssignmentExpression":
+            if (isLogicalAssignmentOperator(node.operator)) {
+                state.pushChoiceContext(
+                    node.operator.slice(0, -1), // removes `=` from the end
+                    isForkingByTrueOrFalse(node)
+                );
+            }
+            break;
+
         case "ConditionalExpression":
         case "IfStatement":
             state.pushChoiceContext("test", false);
@@ -449,6 +508,10 @@ function processCodePathToExit(analyzer, node) {
     let dontForward = false;
 
     switch (node.type) {
+        case "ChainExpression":
+            state.popChainContext();
+            break;
+
         case "IfStatement":
         case "ConditionalExpression":
             state.popChoiceContext();
@@ -460,6 +523,12 @@ function processCodePathToExit(analyzer, node) {
             }
             break;
 
+        case "AssignmentExpression":
+            if (isLogicalAssignmentOperator(node.operator)) {
+                state.popChoiceContext();
+            }
+            break;
+
         case "SwitchStatement":
             state.popSwitchContext();
             break;
@@ -583,6 +652,13 @@ function postprocess(analyzer, node) {
             break;
         }
 
+        // The `arguments.length >= 1` case is in `preprocess` function.
+        case "CallExpression":
+            if (node.optional === true && node.arguments.length === 0) {
+                CodePath.getState(analyzer.codePath).makeOptionalRight();
+            }
+            break;
+
         default:
             break;
     }
index 6b17b25c7fdffdd39107da8b3973da1e57f275f9..ca96ad34189acc042504e90b46189f67efda4faf 100644 (file)
@@ -92,7 +92,6 @@ class CodePathSegment {
         /* istanbul ignore if */
         if (debug.enabled) {
             this.internal.nodes = [];
-            this.internal.exitNodes = [];
         }
     }
 
index 9e760601a0f744202ee19c4ff2d3b3f4094a7555..f75e60e28a807e40ad1e0cc31ed85c1bd1f85d59 100644 (file)
@@ -234,6 +234,7 @@ class CodePathState {
         this.tryContext = null;
         this.loopContext = null;
         this.breakContext = null;
+        this.chainContext = null;
 
         this.currentSegments = [];
         this.initialSegment = this.forkContext.head[0];
@@ -316,7 +317,7 @@ class CodePathState {
     //--------------------------------------------------------------------------
 
     /**
-     * Creates a context for ConditionalExpression, LogicalExpression,
+     * Creates a context for ConditionalExpression, LogicalExpression, AssignmentExpression (logical assignments only),
      * IfStatement, WhileStatement, DoWhileStatement, or ForStatement.
      *
      * LogicalExpressions have cases that it goes different paths between the
@@ -338,7 +339,7 @@ class CodePathState {
      *     a -> b -> foo();
      *     a -> b -> bar();
      * @param {string} kind A kind string.
-     *   If the new context is LogicalExpression's, this is `"&&"` or `"||"`.
+     *   If the new context is LogicalExpression's or AssignmentExpression's, this is `"&&"` or `"||"` or `"??"`.
      *   If it's IfStatement's or ConditionalExpression's, this is `"test"`.
      *   Otherwise, this is `"loop"`.
      * @param {boolean} isForkingAsResult A flag that shows that goes different
@@ -555,6 +556,64 @@ class CodePathState {
         );
     }
 
+    //--------------------------------------------------------------------------
+    // ChainExpression
+    //--------------------------------------------------------------------------
+
+    /**
+     * Push a new `ChainExpression` context to the stack.
+     * This method is called on entering to each `ChainExpression` node.
+     * This context is used to count forking in the optional chain then merge them on the exiting from the `ChainExpression` node.
+     * @returns {void}
+     */
+    pushChainContext() {
+        this.chainContext = {
+            upper: this.chainContext,
+            countChoiceContexts: 0
+        };
+    }
+
+    /**
+     * Pop a `ChainExpression` context from the stack.
+     * This method is called on exiting from each `ChainExpression` node.
+     * This merges all forks of the last optional chaining.
+     * @returns {void}
+     */
+    popChainContext() {
+        const context = this.chainContext;
+
+        this.chainContext = context.upper;
+
+        // pop all choice contexts of this.
+        for (let i = context.countChoiceContexts; i > 0; --i) {
+            this.popChoiceContext();
+        }
+    }
+
+    /**
+     * Create a choice context for optional access.
+     * This method is called on entering to each `(Call|Member)Expression[optional=true]` node.
+     * This creates a choice context as similar to `LogicalExpression[operator="??"]` node.
+     * @returns {void}
+     */
+    makeOptionalNode() {
+        if (this.chainContext) {
+            this.chainContext.countChoiceContexts += 1;
+            this.pushChoiceContext("??", false);
+        }
+    }
+
+    /**
+     * Create a fork.
+     * This method is called on entering to the `arguments|property` property of each `(Call|Member)Expression` node.
+     * @returns {void}
+     */
+    makeOptionalRight() {
+        if (this.chainContext) {
+            this.makeLogicalRight();
+        }
+    }
+
     //--------------------------------------------------------------------------
     // SwitchStatement
     //--------------------------------------------------------------------------
index bde4e0a38ad24bcab94e147d6f066ad2546c0cf1..a4cb99a22e0126d0153008029674fbb02bc1c48f 100644 (file)
@@ -25,6 +25,22 @@ function getId(segment) { // eslint-disable-line jsdoc/require-jsdoc
     return segment.id + (segment.reachable ? "" : "!");
 }
 
+/**
+ * Get string for the given node and operation.
+ * @param {ASTNode} node The node to convert.
+ * @param {"enter" | "exit" | undefined} label The operation label.
+ * @returns {string} The string representation.
+ */
+function nodeToString(node, label) {
+    const suffix = label ? `:${label}` : "";
+
+    switch (node.type) {
+        case "Identifier": return `${node.type}${suffix} (${node.name})`;
+        case "Literal": return `${node.type}${suffix} (${node.value})`;
+        default: return `${node.type}${suffix}`;
+    }
+}
+
 //------------------------------------------------------------------------------
 // Public Interface
 //------------------------------------------------------------------------------
@@ -56,9 +72,15 @@ module.exports = {
             const segInternal = state.currentSegments[i].internal;
 
             if (leaving) {
-                segInternal.exitNodes.push(node);
+                const last = segInternal.nodes.length - 1;
+
+                if (last >= 0 && segInternal.nodes[last] === nodeToString(node, "enter")) {
+                    segInternal.nodes[last] = nodeToString(node, void 0);
+                } else {
+                    segInternal.nodes.push(nodeToString(node, "exit"));
+                }
             } else {
-                segInternal.nodes.push(node);
+                segInternal.nodes.push(nodeToString(node, "enter"));
             }
         }
 
@@ -104,23 +126,8 @@ module.exports = {
                 text += "style=\"rounded,dashed,filled\",fillcolor=\"#FF9800\",label=\"<<unreachable>>\\n";
             }
 
-            if (segment.internal.nodes.length > 0 || segment.internal.exitNodes.length > 0) {
-                text += [].concat(
-                    segment.internal.nodes.map(node => {
-                        switch (node.type) {
-                            case "Identifier": return `${node.type} (${node.name})`;
-                            case "Literal": return `${node.type} (${node.value})`;
-                            default: return node.type;
-                        }
-                    }),
-                    segment.internal.exitNodes.map(node => {
-                        switch (node.type) {
-                            case "Identifier": return `${node.type}:exit (${node.name})`;
-                            case "Literal": return `${node.type}:exit (${node.value})`;
-                            default: return `${node.type}:exit`;
-                        }
-                    })
-                ).join("\\n");
+            if (segment.internal.nodes.length > 0) {
+                text += segment.internal.nodes.join("\\n");
             } else {
                 text += "????";
             }
index 067d02480302cb62c114f77804529fadd9974e09..07bbead281cbf6cabb1712bd3f541967a2752fa2 100644 (file)
@@ -11,7 +11,7 @@
 //------------------------------------------------------------------------------
 
 const levn = require("levn"),
-    ConfigOps = require("../shared/config-ops");
+    ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops");
 
 const debug = require("debug")("eslint:config-comment-parser");
 
index f9f38790b3c4d1aa3bc3407ef45f52f656247291..5c1a8d78aa1df02d7bb9e5561406e46bd29247db 100644 (file)
@@ -16,11 +16,11 @@ const
     evk = require("eslint-visitor-keys"),
     espree = require("espree"),
     lodash = require("lodash"),
-    BuiltInEnvironments = require("../../conf/environments"),
+    BuiltInEnvironments = require("@eslint/eslintrc/conf/environments"),
     pkg = require("../../package.json"),
     astUtils = require("../shared/ast-utils"),
-    ConfigOps = require("../shared/config-ops"),
-    validator = require("../shared/config-validator"),
+    ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"),
+    ConfigValidator = require("@eslint/eslintrc/lib/shared/config-validator"),
     Traverser = require("../shared/traverser"),
     { SourceCode } = require("../source-code"),
     CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
@@ -293,6 +293,9 @@ function getDirectiveComments(filename, ast, ruleMapper, warnInlineConfig) {
     const exportedVariables = {};
     const problems = [];
     const disableDirectives = [];
+    const validator = new ConfigValidator({
+        builtInRules: Rules
+    });
 
     ast.comments.filter(token => token.type !== "Shebang").forEach(comment => {
         const trimmedCommentText = stripDirectiveComment(comment.value);
index eef5165585b21d2e50462d79d30b36140c42eecb..bed5af81e5dcecef105f3f2d61a4fa55fcb41ebe 100644 (file)
@@ -196,15 +196,19 @@ function mapSuggestions(descriptor, sourceCode, messages) {
         return [];
     }
 
-    return descriptor.suggest.map(suggestInfo => {
-        const computedDesc = suggestInfo.desc || messages[suggestInfo.messageId];
-
-        return {
-            ...suggestInfo,
-            desc: interpolate(computedDesc, suggestInfo.data),
-            fix: normalizeFixes(suggestInfo, sourceCode)
-        };
-    });
+    return descriptor.suggest
+        .map(suggestInfo => {
+            const computedDesc = suggestInfo.desc || messages[suggestInfo.messageId];
+
+            return {
+                ...suggestInfo,
+                desc: interpolate(computedDesc, suggestInfo.data),
+                fix: normalizeFixes(suggestInfo, sourceCode)
+            };
+        })
+
+        // Remove suggestions that didn't provide a fix
+        .filter(({ fix }) => fix);
 }
 
 /**
index 77df1def893ccc57046b5a94c61e62f0570434dc..905f3418121882cc5dfabd2f319e7edc430d077b 100644 (file)
@@ -644,6 +644,10 @@ class RuleTester {
             assert.ok(item.errors || item.errors === 0,
                 `Did not specify errors for an invalid test of ${ruleName}`);
 
+            if (Array.isArray(item.errors) && item.errors.length === 0) {
+                assert.fail("Invalid cases must have at least one error");
+            }
+
             const ruleHasMetaMessages = hasOwnProperty(rule, "meta") && hasOwnProperty(rule.meta, "messages");
             const friendlyIDList = ruleHasMetaMessages ? `[${Object.keys(rule.meta.messages).map(key => `'${key}'`).join(", ")}]` : null;
 
@@ -651,6 +655,11 @@ class RuleTester {
             const messages = result.messages;
 
             if (typeof item.errors === "number") {
+
+                if (item.errors === 0) {
+                    assert.fail("Invalid cases must have 'error' value greater than 0");
+                }
+
                 assert.strictEqual(messages.length, item.errors, util.format("Should have %d error%s but had %d: %s",
                     item.errors, item.errors === 1 ? "" : "s", messages.length, util.inspect(messages)));
             } else {
@@ -852,6 +861,16 @@ class RuleTester {
                 );
             }
 
+            // Rules that produce fixes must have `meta.fixable` property.
+            if (result.output !== item.code) {
+                assert.ok(
+                    hasOwnProperty(rule, "meta"),
+                    "Fixable rules should export a `meta.fixable` property."
+                );
+
+                // Linter throws if a rule that produced a fix has `meta` but doesn't have `meta.fixable`.
+            }
+
             assertASTDidntChange(result.beforeAST, result.afterAST);
         }
 
index cf994ad257446bb7ff27b121a9b9ff7b8976d9b7..0e0d07a00c971da3dc9e4373fcb18d75633ae8a3 100644 (file)
@@ -86,16 +86,6 @@ function isAccessorKind(node) {
     return node.kind === "get" || node.kind === "set";
 }
 
-/**
- * Checks whether or not a given node is an `Identifier` node which was named a given name.
- * @param {ASTNode} node A node to check.
- * @param {string} name An expected name of the node.
- * @returns {boolean} `true` if the node is an `Identifier` node which was named as expected.
- */
-function isIdentifier(node, name) {
-    return node.type === "Identifier" && node.name === name;
-}
-
 /**
  * Checks whether or not a given node is an argument of a specified method call.
  * @param {ASTNode} node A node to check.
@@ -109,10 +99,7 @@ function isArgumentOfMethodCall(node, index, object, property) {
 
     return (
         parent.type === "CallExpression" &&
-        parent.callee.type === "MemberExpression" &&
-        parent.callee.computed === false &&
-        isIdentifier(parent.callee.object, object) &&
-        isIdentifier(parent.callee.property, property) &&
+        astUtils.isSpecificMemberAccess(parent.callee, object, property) &&
         parent.arguments[index] === node
     );
 }
index 62ba7b72d8725726a4578f725492d7524df35a81..7267347149da9aea2175cf837ae7e5a5762e2fda 100644 (file)
@@ -9,8 +9,6 @@
 // Requirements
 //------------------------------------------------------------------------------
 
-const lodash = require("lodash");
-
 const astUtils = require("./utils/ast-utils");
 
 //------------------------------------------------------------------------------
@@ -30,17 +28,27 @@ function isReachable(segment) {
 }
 
 /**
- * Checks a given node is a MemberExpression node which has the specified name's
+ * Checks a given node is a member access which has the specified name's
  * property.
  * @param {ASTNode} node A node to check.
- * @returns {boolean} `true` if the node is a MemberExpression node which has
- *      the specified name's property
+ * @returns {boolean} `true` if the node is a member access which has
+ *      the specified name's property. The node may be a `(Chain|Member)Expression` node.
  */
 function isTargetMethod(node) {
-    return (
-        node.type === "MemberExpression" &&
-        TARGET_METHODS.test(astUtils.getStaticPropertyName(node) || "")
-    );
+    return astUtils.isSpecificMemberAccess(node, null, TARGET_METHODS);
+}
+
+/**
+ * Returns a human-legible description of an array method
+ * @param {string} arrayMethodName A method name to fully qualify
+ * @returns {string} the method name prefixed with `Array.` if it is a class method,
+ *      or else `Array.prototype.` if it is an instance method.
+ */
+function fullMethodName(arrayMethodName) {
+    if (["from", "of", "isArray"].includes(arrayMethodName)) {
+        return "Array.".concat(arrayMethodName);
+    }
+    return "Array.prototype.".concat(arrayMethodName);
 }
 
 /**
@@ -65,6 +73,7 @@ function getArrayMethodName(node) {
              */
             case "LogicalExpression":
             case "ConditionalExpression":
+            case "ChainExpression":
                 currentNode = parent;
                 break;
 
@@ -153,10 +162,10 @@ module.exports = {
         ],
 
         messages: {
-            expectedAtEnd: "Expected to return a value at the end of {{name}}.",
-            expectedInside: "Expected to return a value in {{name}}.",
-            expectedReturnValue: "{{name}} expected a return value.",
-            expectedNoReturnValue: "{{name}} did not expect a return value."
+            expectedAtEnd: "{{arrayMethodName}}() expects a value to be returned at the end of {{name}}.",
+            expectedInside: "{{arrayMethodName}}() expects a return value from {{name}}.",
+            expectedReturnValue: "{{arrayMethodName}}() expects a return value from {{name}}.",
+            expectedNoReturnValue: "{{arrayMethodName}}() expects no useless return value from {{name}}."
         }
     },
 
@@ -202,14 +211,13 @@ module.exports = {
             }
 
             if (messageId) {
-                let name = astUtils.getFunctionNameWithKind(node);
+                const name = astUtils.getFunctionNameWithKind(node);
 
-                name = messageId === "expectedNoReturnValue" ? lodash.upperFirst(name) : name;
                 context.report({
                     node,
                     loc: astUtils.getFunctionHeadLoc(node, sourceCode),
                     messageId,
-                    data: { name }
+                    data: { name, arrayMethodName: fullMethodName(funcInfo.arrayMethodName) }
                 });
             }
         }
@@ -273,7 +281,8 @@ module.exports = {
                         node,
                         messageId,
                         data: {
-                            name: lodash.upperFirst(astUtils.getFunctionNameWithKind(funcInfo.node))
+                            name: astUtils.getFunctionNameWithKind(funcInfo.node),
+                            arrayMethodName: fullMethodName(funcInfo.arrayMethodName)
                         }
                     });
                 }
index 9d5c77d8573d6abefdb5ab4d69e94d917bd7b44b..7b318ea8b3a165e0e5cc7b79b55f42a1622db659 100644 (file)
@@ -75,6 +75,7 @@ module.exports = {
         const never = options[0] === "never";
         const requireReturnForObjectLiteral = options[1] && options[1].requireReturnForObjectLiteral;
         const sourceCode = context.getSourceCode();
+        let funcInfo = null;
 
         /**
          * Checks whether the given node has ASI problem or not.
@@ -99,6 +100,21 @@ module.exports = {
             return sourceCode.getTokenAfter(node);
         }
 
+        /**
+         * Check whether the node is inside of a for loop's init
+         * @param {ASTNode} node node is inside for loop
+         * @returns {boolean} `true` if the node is inside of a for loop, else `false`
+         */
+        function isInsideForLoopInitializer(node) {
+            if (node && node.parent) {
+                if (node.parent.type === "ForStatement" && node.parent.init === node) {
+                    return true;
+                }
+                return isInsideForLoopInitializer(node.parent);
+            }
+            return false;
+        }
+
         /**
          * Determines whether a arrow function body needs braces
          * @param {ASTNode} node The arrow function node.
@@ -136,7 +152,7 @@ module.exports = {
 
                     context.report({
                         node,
-                        loc: arrowBody.loc.start,
+                        loc: arrowBody.loc,
                         messageId,
                         fix(fixer) {
                             const fixes = [];
@@ -178,11 +194,13 @@ module.exports = {
                              * If the first token of the reutrn value is `{` or the return value is a sequence expression,
                              * enclose the return value by parentheses to avoid syntax error.
                              */
-                            if (astUtils.isOpeningBraceToken(firstValueToken) || blockBody[0].argument.type === "SequenceExpression") {
-                                fixes.push(
-                                    fixer.insertTextBefore(firstValueToken, "("),
-                                    fixer.insertTextAfter(lastValueToken, ")")
-                                );
+                            if (astUtils.isOpeningBraceToken(firstValueToken) || blockBody[0].argument.type === "SequenceExpression" || (funcInfo.hasInOperator && isInsideForLoopInitializer(node))) {
+                                if (!astUtils.isParenthesised(sourceCode, blockBody[0].argument)) {
+                                    fixes.push(
+                                        fixer.insertTextBefore(firstValueToken, "("),
+                                        fixer.insertTextAfter(lastValueToken, ")")
+                                    );
+                                }
                             }
 
                             /*
@@ -201,7 +219,7 @@ module.exports = {
                 if (always || (asNeeded && requireReturnForObjectLiteral && arrowBody.type === "ObjectExpression")) {
                     context.report({
                         node,
-                        loc: arrowBody.loc.start,
+                        loc: arrowBody.loc,
                         messageId: "expectedBlock",
                         fix(fixer) {
                             const fixes = [];
@@ -245,7 +263,24 @@ module.exports = {
         }
 
         return {
-            "ArrowFunctionExpression:exit": validate
+            "BinaryExpression[operator='in']"() {
+                let info = funcInfo;
+
+                while (info) {
+                    info.hasInOperator = true;
+                    info = info.upper;
+                }
+            },
+            ArrowFunctionExpression() {
+                funcInfo = {
+                    upper: funcInfo,
+                    hasInOperator: false
+                };
+            },
+            "ArrowFunctionExpression:exit"(node) {
+                validate(node);
+                funcInfo = funcInfo.upper;
+            }
         };
     }
 };
index bfd32447ac6f3d9dec93fd9f5ce5b2df874b1068..eaa1aab023889ebb1a40abdce393b9ca988b9fd8 100644 (file)
@@ -15,15 +15,12 @@ const astUtils = require("./utils/ast-utils");
 //------------------------------------------------------------------------------
 
 /**
- * Get location should be reported by AST node.
- * @param {ASTNode} node AST Node.
- * @returns {Location} Location information.
+ * Determines if the given arrow function has block body.
+ * @param {ASTNode} node `ArrowFunctionExpression` node.
+ * @returns {boolean} `true` if the function has block body.
  */
-function getLocation(node) {
-    return {
-        start: node.params[0].loc.start,
-        end: node.params[node.params.length - 1].loc.end
-    };
+function hasBlockBody(node) {
+    return node.body.type === "BlockStatement";
 }
 
 //------------------------------------------------------------------------------
@@ -75,126 +72,112 @@ module.exports = {
         const sourceCode = context.getSourceCode();
 
         /**
-         * Determines whether a arrow function argument end with `)`
-         * @param {ASTNode} node The arrow function node.
-         * @returns {void}
+         * Finds opening paren of parameters for the given arrow function, if it exists.
+         * It is assumed that the given arrow function has exactly one parameter.
+         * @param {ASTNode} node `ArrowFunctionExpression` node.
+         * @returns {Token|null} the opening paren, or `null` if the given arrow function doesn't have parens of parameters.
          */
-        function parens(node) {
-            const isAsync = node.async;
-            const firstTokenOfParam = sourceCode.getFirstToken(node, isAsync ? 1 : 0);
-
-            /**
-             * Remove the parenthesis around a parameter
-             * @param {Fixer} fixer Fixer
-             * @returns {string} fixed parameter
-             */
-            function fixParamsWithParenthesis(fixer) {
-                const paramToken = sourceCode.getTokenAfter(firstTokenOfParam);
-
-                /*
-                 * ES8 allows Trailing commas in function parameter lists and calls
-                 * https://github.com/eslint/eslint/issues/8834
-                 */
-                const closingParenToken = sourceCode.getTokenAfter(paramToken, astUtils.isClosingParenToken);
-                const asyncToken = isAsync ? sourceCode.getTokenBefore(firstTokenOfParam) : null;
-                const shouldAddSpaceForAsync = asyncToken && (asyncToken.range[1] === firstTokenOfParam.range[0]);
-
-                return fixer.replaceTextRange([
-                    firstTokenOfParam.range[0],
-                    closingParenToken.range[1]
-                ], `${shouldAddSpaceForAsync ? " " : ""}${paramToken.value}`);
+        function findOpeningParenOfParams(node) {
+            const tokenBeforeParams = sourceCode.getTokenBefore(node.params[0]);
+
+            if (
+                tokenBeforeParams &&
+                astUtils.isOpeningParenToken(tokenBeforeParams) &&
+                node.range[0] <= tokenBeforeParams.range[0]
+            ) {
+                return tokenBeforeParams;
             }
 
-            /**
-             * Checks whether there are comments inside the params or not.
-             * @returns {boolean} `true` if there are comments inside of parens, else `false`
-             */
-            function hasCommentsInParens() {
-                if (astUtils.isOpeningParenToken(firstTokenOfParam)) {
-                    const closingParenToken = sourceCode.getTokenAfter(node.params[0], astUtils.isClosingParenToken);
+            return null;
+        }
 
-                    return closingParenToken && sourceCode.commentsExistBetween(firstTokenOfParam, closingParenToken);
-                }
-                return false;
+        /**
+         * Finds closing paren of parameters for the given arrow function.
+         * It is assumed that the given arrow function has parens of parameters and that it has exactly one parameter.
+         * @param {ASTNode} node `ArrowFunctionExpression` node.
+         * @returns {Token} the closing paren of parameters.
+         */
+        function getClosingParenOfParams(node) {
+            return sourceCode.getTokenAfter(node.params[0], astUtils.isClosingParenToken);
+        }
 
-            }
+        /**
+         * Determines whether the given arrow function has comments inside parens of parameters.
+         * It is assumed that the given arrow function has parens of parameters.
+         * @param {ASTNode} node `ArrowFunctionExpression` node.
+         * @param {Token} openingParen Opening paren of parameters.
+         * @returns {boolean} `true` if the function has at least one comment inside of parens of parameters.
+         */
+        function hasCommentsInParensOfParams(node, openingParen) {
+            return sourceCode.commentsExistBetween(openingParen, getClosingParenOfParams(node));
+        }
 
-            if (hasCommentsInParens()) {
-                return;
-            }
+        /**
+         * Determines whether the given arrow function has unexpected tokens before opening paren of parameters,
+         * in which case it will be assumed that the existing parens of parameters are necessary.
+         * Only tokens within the range of the arrow function (tokens that are part of the arrow function) are taken into account.
+         * Example: <T>(a) => b
+         * @param {ASTNode} node `ArrowFunctionExpression` node.
+         * @param {Token} openingParen Opening paren of parameters.
+         * @returns {boolean} `true` if the function has at least one unexpected token.
+         */
+        function hasUnexpectedTokensBeforeOpeningParen(node, openingParen) {
+            const expectedCount = node.async ? 1 : 0;
 
-            // "as-needed", { "requireForBlockBody": true }: x => x
-            if (
-                requireForBlockBody &&
-                node.params[0].type === "Identifier" &&
-                !node.params[0].typeAnnotation &&
-                node.body.type !== "BlockStatement" &&
-                !node.returnType
-            ) {
-                if (astUtils.isOpeningParenToken(firstTokenOfParam)) {
-                    context.report({
-                        node,
-                        messageId: "unexpectedParensInline",
-                        loc: getLocation(node),
-                        fix: fixParamsWithParenthesis
-                    });
-                }
-                return;
-            }
+            return sourceCode.getFirstToken(node, { skip: expectedCount }) !== openingParen;
+        }
 
-            if (
-                requireForBlockBody &&
-                node.body.type === "BlockStatement"
-            ) {
-                if (!astUtils.isOpeningParenToken(firstTokenOfParam)) {
-                    context.report({
-                        node,
-                        messageId: "expectedParensBlock",
-                        loc: getLocation(node),
-                        fix(fixer) {
-                            return fixer.replaceText(firstTokenOfParam, `(${firstTokenOfParam.value})`);
-                        }
-                    });
-                }
-                return;
-            }
+        return {
+            "ArrowFunctionExpression[params.length=1]"(node) {
+                const shouldHaveParens = !asNeeded || requireForBlockBody && hasBlockBody(node);
+                const openingParen = findOpeningParenOfParams(node);
+                const hasParens = openingParen !== null;
+                const [param] = node.params;
 
-            // "as-needed": x => x
-            if (asNeeded &&
-                node.params[0].type === "Identifier" &&
-                !node.params[0].typeAnnotation &&
-                !node.returnType
-            ) {
-                if (astUtils.isOpeningParenToken(firstTokenOfParam)) {
+                if (shouldHaveParens && !hasParens) {
                     context.report({
                         node,
-                        messageId: "unexpectedParens",
-                        loc: getLocation(node),
-                        fix: fixParamsWithParenthesis
+                        messageId: requireForBlockBody ? "expectedParensBlock" : "expectedParens",
+                        loc: param.loc,
+                        *fix(fixer) {
+                            yield fixer.insertTextBefore(param, "(");
+                            yield fixer.insertTextAfter(param, ")");
+                        }
                     });
                 }
-                return;
-            }
 
-            if (firstTokenOfParam.type === "Identifier") {
-                const after = sourceCode.getTokenAfter(firstTokenOfParam);
-
-                // (x) => x
-                if (after.value !== ")") {
+                if (
+                    !shouldHaveParens &&
+                    hasParens &&
+                    param.type === "Identifier" &&
+                    !param.typeAnnotation &&
+                    !node.returnType &&
+                    !hasCommentsInParensOfParams(node, openingParen) &&
+                    !hasUnexpectedTokensBeforeOpeningParen(node, openingParen)
+                ) {
                     context.report({
                         node,
-                        messageId: "expectedParens",
-                        loc: getLocation(node),
-                        fix(fixer) {
-                            return fixer.replaceText(firstTokenOfParam, `(${firstTokenOfParam.value})`);
+                        messageId: requireForBlockBody ? "unexpectedParensInline" : "unexpectedParens",
+                        loc: param.loc,
+                        *fix(fixer) {
+                            const tokenBeforeOpeningParen = sourceCode.getTokenBefore(openingParen);
+                            const closingParen = getClosingParenOfParams(node);
+
+                            if (
+                                tokenBeforeOpeningParen &&
+                                tokenBeforeOpeningParen.range[1] === openingParen.range[0] &&
+                                !astUtils.canTokensBeAdjacent(tokenBeforeOpeningParen, sourceCode.getFirstToken(param))
+                            ) {
+                                yield fixer.insertTextBefore(openingParen, " ");
+                            }
+
+                            // remove parens, whitespace inside parens, and possible trailing comma
+                            yield fixer.removeRange([openingParen.range[0], param.range[0]]);
+                            yield fixer.removeRange([param.range[1], closingParen.range[1]]);
                         }
                     });
                 }
             }
-        }
-
-        return {
-            "ArrowFunctionExpression[params.length=1]": parens
         };
     }
 };
index 04360837294a127b2ba6f2b2031b7dd2f0af3c3b..d34656cfabe731b7516475be66531f3fa904c954 100644 (file)
@@ -32,6 +32,10 @@ module.exports = {
                         type: "boolean",
                         default: false
                     },
+                    ignoreGlobals: {
+                        type: "boolean",
+                        default: false
+                    },
                     properties: {
                         enum: ["always", "never"]
                     },
@@ -61,8 +65,11 @@ module.exports = {
         let properties = options.properties || "";
         const ignoreDestructuring = options.ignoreDestructuring;
         const ignoreImports = options.ignoreImports;
+        const ignoreGlobals = options.ignoreGlobals;
         const allow = options.allow || [];
 
+        let globalScope;
+
         if (properties !== "always" && properties !== "never") {
             properties = "always";
         }
@@ -159,6 +166,37 @@ module.exports = {
             return false;
         }
 
+        /**
+         * Checks whether the given node represents a reference to a global variable that is not declared in the source code.
+         * These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables.
+         * @param {ASTNode} node `Identifier` node to check.
+         * @returns {boolean} `true` if the node is a reference to a global variable.
+         */
+        function isReferenceToGlobalVariable(node) {
+            const variable = globalScope.set.get(node.name);
+
+            return variable && variable.defs.length === 0 &&
+                variable.references.some(ref => ref.identifier === node);
+        }
+
+        /**
+         * Checks whether the given node represents a reference to a property of an object in an object literal expression.
+         * This allows to differentiate between a global variable that is allowed to be used as a reference, and the key
+         * of the expressed object (which shouldn't be allowed).
+         * @param {ASTNode} node `Identifier` node to check.
+         * @returns {boolean} `true` if the node is a property name of an object literal expression
+         */
+        function isPropertyNameInObjectLiteral(node) {
+            const parent = node.parent;
+
+            return (
+                parent.type === "Property" &&
+                parent.parent.type === "ObjectExpression" &&
+                !parent.computed &&
+                parent.key === node
+            );
+        }
+
         /**
          * Reports an AST node as a rule violation.
          * @param {ASTNode} node The node to report.
@@ -174,6 +212,10 @@ module.exports = {
 
         return {
 
+            Program() {
+                globalScope = context.getScope();
+            },
+
             Identifier(node) {
 
                 /*
@@ -189,6 +231,11 @@ module.exports = {
                     return;
                 }
 
+                // Check if it's a global variable
+                if (ignoreGlobals && isReferenceToGlobalVariable(node) && !isPropertyNameInObjectLiteral(node)) {
+                    return;
+                }
+
                 // MemberExpressions get special rules
                 if (node.parent.type === "MemberExpression") {
 
index 22667fa47077247f8ab5d42c0598a2b943d1db02..94db253d25bacb654e2f4cb67102f3d8321a7d27 100644 (file)
@@ -9,23 +9,12 @@
 //------------------------------------------------------------------------------
 
 const lodash = require("lodash");
-
 const astUtils = require("./utils/ast-utils");
 
 //------------------------------------------------------------------------------
 // Helpers
 //------------------------------------------------------------------------------
 
-/**
- * Checks whether or not a given node is an `Identifier` node which was named a given name.
- * @param {ASTNode} node A node to check.
- * @param {string} name An expected name of the node.
- * @returns {boolean} `true` if the node is an `Identifier` node which was named as expected.
- */
-function isIdentifier(node, name) {
-    return node.type === "Identifier" && node.name === name;
-}
-
 /**
  * Checks whether or not a given code path segment is unreachable.
  * @param {CodePathSegment} segment A CodePathSegment to check.
@@ -165,7 +154,7 @@ module.exports = {
                 let hasReturnValue = Boolean(argument);
 
                 if (treatUndefinedAsUnspecified && hasReturnValue) {
-                    hasReturnValue = !isIdentifier(argument, "undefined") && argument.operator !== "void";
+                    hasReturnValue = !astUtils.isSpecificId(argument, "undefined") && argument.operator !== "void";
                 }
 
                 if (!funcInfo.hasReturn) {
index 5a848f210cae52d4ee6ecb31a368bc5e8b51ab75..dfec18fb65a4b13090605b50f9b0643c900bd01a 100644 (file)
@@ -50,6 +50,7 @@ function isPossibleConstructor(node) {
         case "MemberExpression":
         case "CallExpression":
         case "NewExpression":
+        case "ChainExpression":
         case "YieldExpression":
         case "TaggedTemplateExpression":
         case "MetaProperty":
@@ -59,9 +60,36 @@ function isPossibleConstructor(node) {
             return node.name !== "undefined";
 
         case "AssignmentExpression":
-            return isPossibleConstructor(node.right);
+            if (["=", "&&="].includes(node.operator)) {
+                return isPossibleConstructor(node.right);
+            }
+
+            if (["||=", "??="].includes(node.operator)) {
+                return (
+                    isPossibleConstructor(node.left) ||
+                    isPossibleConstructor(node.right)
+                );
+            }
+
+            /**
+             * All other assignment operators are mathematical assignment operators (arithmetic or bitwise).
+             * An assignment expression with a mathematical operator can either evaluate to a primitive value,
+             * or throw, depending on the operands. Thus, it cannot evaluate to a constructor function.
+             */
+            return false;
 
         case "LogicalExpression":
+
+            /*
+             * If the && operator short-circuits, the left side was falsy and therefore not a constructor, and if
+             * it doesn't short-circuit, it takes the value from the right side, so the right side must always be a
+             * possible constructor. A future improvement could verify that the left side could be truthy by
+             * excluding falsy literals.
+             */
+            if (node.operator === "&&") {
+                return isPossibleConstructor(node.right);
+            }
+
             return (
                 isPossibleConstructor(node.left) ||
                 isPossibleConstructor(node.right)
index 29f00c0ad0b617ebfa765b3d48e7289355fde30e..92d31a6476e4359ed4fda9b0e3622d33eb1b0b77 100644 (file)
@@ -457,11 +457,18 @@ module.exports = {
 
         return {
             IfStatement(node) {
-                if (node.parent.type !== "IfStatement") {
+                const parent = node.parent;
+                const isElseIf = parent.type === "IfStatement" && parent.alternate === node;
+
+                if (!isElseIf) {
+
+                    // This is a top `if`, check the whole `if-else-if` chain
                     prepareIfChecks(node).forEach(preparedCheck => {
                         preparedCheck.check();
                     });
                 }
+
+                // Skip `else if`, it's already checked (when the top `if` was visited)
             },
 
             WhileStatement(node) {
index d483e217a94980aa9b9d382d57d7d2612edf2e2a..0a739b1712b902949b471c258ae8bf1a35de1b47 100644 (file)
@@ -52,31 +52,37 @@ module.exports = {
          */
         function checkDotLocation(node) {
             const property = node.property;
-            const dot = sourceCode.getTokenBefore(property);
-
-            // `obj` expression can be parenthesized, but those paren tokens are not a part of the `obj` node.
-            const tokenBeforeDot = sourceCode.getTokenBefore(dot);
-
-            const textBeforeDot = sourceCode.getText().slice(tokenBeforeDot.range[1], dot.range[0]);
-            const textAfterDot = sourceCode.getText().slice(dot.range[1], property.range[0]);
+            const dotToken = sourceCode.getTokenBefore(property);
 
             if (onObject) {
-                if (!astUtils.isTokenOnSameLine(tokenBeforeDot, dot)) {
-                    const neededTextAfterToken = astUtils.isDecimalIntegerNumericToken(tokenBeforeDot) ? " " : "";
 
+                // `obj` expression can be parenthesized, but those paren tokens are not a part of the `obj` node.
+                const tokenBeforeDot = sourceCode.getTokenBefore(dotToken);
+
+                if (!astUtils.isTokenOnSameLine(tokenBeforeDot, dotToken)) {
                     context.report({
                         node,
-                        loc: dot.loc,
+                        loc: dotToken.loc,
                         messageId: "expectedDotAfterObject",
-                        fix: fixer => fixer.replaceTextRange([tokenBeforeDot.range[1], property.range[0]], `${neededTextAfterToken}.${textBeforeDot}${textAfterDot}`)
+                        *fix(fixer) {
+                            if (dotToken.value.startsWith(".") && astUtils.isDecimalIntegerNumericToken(tokenBeforeDot)) {
+                                yield fixer.insertTextAfter(tokenBeforeDot, ` ${dotToken.value}`);
+                            } else {
+                                yield fixer.insertTextAfter(tokenBeforeDot, dotToken.value);
+                            }
+                            yield fixer.remove(dotToken);
+                        }
                     });
                 }
-            } else if (!astUtils.isTokenOnSameLine(dot, property)) {
+            } else if (!astUtils.isTokenOnSameLine(dotToken, property)) {
                 context.report({
                     node,
-                    loc: dot.loc,
+                    loc: dotToken.loc,
                     messageId: "expectedDotBeforeProperty",
-                    fix: fixer => fixer.replaceTextRange([tokenBeforeDot.range[1], property.range[0]], `${textBeforeDot}${textAfterDot}.`)
+                    *fix(fixer) {
+                        yield fixer.remove(dotToken);
+                        yield fixer.insertTextBefore(property, dotToken.value);
+                    }
                 });
             }
         }
index 2e8fff8b90e77d9529160749f52013d0b5c6b482..751b4628edc4d5bef1eca0f333cb9c28e96c1c58 100644 (file)
@@ -87,28 +87,36 @@ module.exports = {
                     data: {
                         key: formattedValue
                     },
-                    fix(fixer) {
+                    *fix(fixer) {
                         const leftBracket = sourceCode.getTokenAfter(node.object, astUtils.isOpeningBracketToken);
                         const rightBracket = sourceCode.getLastToken(node);
+                        const nextToken = sourceCode.getTokenAfter(node);
 
-                        if (sourceCode.getFirstTokenBetween(leftBracket, rightBracket, { includeComments: true, filter: astUtils.isCommentToken })) {
-
-                            // Don't perform any fixes if there are comments inside the brackets.
-                            return null;
+                        // Don't perform any fixes if there are comments inside the brackets.
+                        if (sourceCode.commentsExistBetween(leftBracket, rightBracket)) {
+                            return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
                         }
 
-                        const tokenAfterProperty = sourceCode.getTokenAfter(rightBracket);
-                        const needsSpaceAfterProperty = tokenAfterProperty &&
-                            rightBracket.range[1] === tokenAfterProperty.range[0] &&
-                            !astUtils.canTokensBeAdjacent(String(value), tokenAfterProperty);
-
-                        const textBeforeDot = astUtils.isDecimalInteger(node.object) ? " " : "";
-                        const textAfterProperty = needsSpaceAfterProperty ? " " : "";
-
-                        return fixer.replaceTextRange(
+                        // Replace the brackets by an identifier.
+                        if (!node.optional) {
+                            yield fixer.insertTextBefore(
+                                leftBracket,
+                                astUtils.isDecimalInteger(node.object) ? " ." : "."
+                            );
+                        }
+                        yield fixer.replaceTextRange(
                             [leftBracket.range[0], rightBracket.range[1]],
-                            `${textBeforeDot}.${value}${textAfterProperty}`
+                            value
                         );
+
+                        // Insert a space after the property if it will be connected to the next token.
+                        if (
+                            nextToken &&
+                            rightBracket.range[1] === nextToken.range[0] &&
+                            !astUtils.canTokensBeAdjacent(String(value), nextToken)
+                        ) {
+                            yield fixer.insertTextAfter(node, " ");
+                        }
                     }
                 });
             }
@@ -141,29 +149,24 @@ module.exports = {
                         data: {
                             key: node.property.name
                         },
-                        fix(fixer) {
-                            const dot = sourceCode.getTokenBefore(node.property);
-                            const textAfterDot = sourceCode.text.slice(dot.range[1], node.property.range[0]);
-
-                            if (textAfterDot.trim()) {
+                        *fix(fixer) {
+                            const dotToken = sourceCode.getTokenBefore(node.property);
 
-                                // Don't perform any fixes if there are comments between the dot and the property name.
-                                return null;
+                            // A statement that starts with `let[` is parsed as a destructuring variable declaration, not a MemberExpression.
+                            if (node.object.type === "Identifier" && node.object.name === "let" && !node.optional) {
+                                return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
                             }
 
-                            if (node.object.type === "Identifier" && node.object.name === "let") {
-
-                                /*
-                                 * A statement that starts with `let[` is parsed as a destructuring variable declaration, not
-                                 * a MemberExpression.
-                                 */
-                                return null;
+                            // Don't perform any fixes if there are comments between the dot and the property name.
+                            if (sourceCode.commentsExistBetween(dotToken, node.property)) {
+                                return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
                             }
 
-                            return fixer.replaceTextRange(
-                                [dot.range[0], node.property.range[1]],
-                                `[${textAfterDot}"${node.property.name}"]`
-                            );
+                            // Replace the identifier to brackets.
+                            if (!node.optional) {
+                                yield fixer.remove(dotToken);
+                            }
+                            yield fixer.replaceText(node.property, `["${node.property.name}"]`);
                         }
                     });
                 }
index 5ecb63ecfa7b6053380769acae80c55708331257..8fe690d4a6ba21bb7321852a0da22134e516410b 100644 (file)
@@ -126,15 +126,24 @@ module.exports = {
                     messageId: "unexpectedWhitespace",
                     fix(fixer) {
 
+                        // Don't remove comments.
+                        if (sourceCode.commentsExistBetween(leftToken, rightToken)) {
+                            return null;
+                        }
+
+                        // If `?.` exsits, it doesn't hide no-undexpected-multiline errors
+                        if (node.optional) {
+                            return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], "?.");
+                        }
+
                         /*
                          * Only autofix if there is no newline
                          * https://github.com/eslint/eslint/issues/7787
                          */
-                        if (!hasNewline) {
-                            return fixer.removeRange([leftToken.range[1], rightToken.range[0]]);
+                        if (hasNewline) {
+                            return null;
                         }
-
-                        return null;
+                        return fixer.removeRange([leftToken.range[1], rightToken.range[0]]);
                     }
                 });
             } else if (!never && !hasWhitespace) {
@@ -149,6 +158,9 @@ module.exports = {
                     },
                     messageId: "missing",
                     fix(fixer) {
+                        if (node.optional) {
+                            return null; // Not sure if inserting a space to either before/after `?.` token.
+                        }
                         return fixer.insertTextBefore(rightToken, " ");
                     }
                 });
@@ -161,7 +173,31 @@ module.exports = {
                     },
                     messageId: "unexpectedNewline",
                     fix(fixer) {
-                        return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ");
+
+                        /*
+                         * Only autofix if there is no newline
+                         * https://github.com/eslint/eslint/issues/7787
+                         * But if `?.` exsits, it doesn't hide no-undexpected-multiline errors
+                         */
+                        if (!node.optional) {
+                            return null;
+                        }
+
+                        // Don't remove comments.
+                        if (sourceCode.commentsExistBetween(leftToken, rightToken)) {
+                            return null;
+                        }
+
+                        const range = [leftToken.range[1], rightToken.range[0]];
+                        const qdToken = sourceCode.getTokenAfter(leftToken);
+
+                        if (qdToken.range[0] === leftToken.range[1]) {
+                            return fixer.replaceTextRange(range, "?. ");
+                        }
+                        if (qdToken.range[1] === rightToken.range[0]) {
+                            return fixer.replaceTextRange(range, " ?.");
+                        }
+                        return fixer.replaceTextRange(range, " ?. ");
                     }
                 });
             }
@@ -172,7 +208,7 @@ module.exports = {
                 const lastToken = sourceCode.getLastToken(node);
                 const lastCalleeToken = sourceCode.getLastToken(node.callee);
                 const parenToken = sourceCode.getFirstTokenBetween(lastCalleeToken, lastToken, astUtils.isOpeningParenToken);
-                const prevToken = parenToken && sourceCode.getTokenBefore(parenToken);
+                const prevToken = parenToken && sourceCode.getTokenBefore(parenToken, astUtils.isNotQuestionDotToken);
 
                 // Parens in NewExpression are optional
                 if (!(parenToken && parenToken.range[1] < node.range[1])) {
index 83430ffadfcdbf40498175d16adc56fdd3c7df4f..755c2ee5075c6b845b0cf550e09c33b35753b4aa 100644 (file)
@@ -117,10 +117,7 @@ module.exports = {
             if (!node) {
                 return false;
             }
-            return node.type === "CallExpression" &&
-                node.callee.type === "MemberExpression" &&
-                node.callee.object.name === objName &&
-                node.callee.property.name === funcName;
+            return node.type === "CallExpression" && astUtils.isSpecificMemberAccess(node.callee, objName, funcName);
         }
 
         /**
index 894c8e331a716815c2e5d460205074cdc4c5658e..9d8d67ba14148d4f62dbe38abc1e7bd54bfebe2f 100644 (file)
@@ -218,7 +218,7 @@ module.exports = {
                 }
 
                 case "ArrowFunctionExpression": {
-                    const firstToken = sourceCode.getFirstToken(node);
+                    const firstToken = sourceCode.getFirstToken(node, { skip: (node.async ? 1 : 0) });
 
                     if (!astUtils.isOpeningParenToken(firstToken)) {
 
index 469c0175d2556b5bdb4a319174e554729867fd9f..09d0332007e1745bd7b1f22fe2f9512ced2713b3 100644 (file)
@@ -13,7 +13,8 @@ const ACCEPTABLE_PARENTS = [
     "CallExpression",
     "ConditionalExpression",
     "Program",
-    "VariableDeclaration"
+    "VariableDeclaration",
+    "ChainExpression"
 ];
 
 /**
index d77a35d41b670d324635fe5e23a5c31592a5df07..4fbba909fde453c5d792aad25985b4b05ef48e95 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * @fileoverview Rule that warns when identifier names that are
- * blacklisted in the configuration are used.
+ * specified in the configuration are used.
  * @author Keith Cirkel (http://keithcirkel.co.uk)
  */
 
@@ -111,6 +111,9 @@ function isShorthandPropertyDefinition(node) {
 
 module.exports = {
     meta: {
+        deprecated: true,
+        replacedBy: ["id-denylist"],
+
         type: "suggestion",
 
         docs: {
@@ -128,25 +131,25 @@ module.exports = {
             uniqueItems: true
         },
         messages: {
-            blacklisted: "Identifier '{{name}}' is blacklisted."
+            restricted: "Identifier '{{name}}' is restricted."
         }
     },
 
     create(context) {
 
-        const blacklist = new Set(context.options);
+        const denyList = new Set(context.options);
         const reportedNodes = new Set();
 
         let globalScope;
 
         /**
-         * Checks whether the given name is blacklisted.
+         * Checks whether the given name is restricted.
          * @param {string} name The name to check.
-         * @returns {boolean} `true` if the name is blacklisted.
+         * @returns {boolean} `true` if the name is restricted.
          * @private
          */
-        function isBlacklisted(name) {
-            return blacklist.has(name);
+        function isRestricted(name) {
+            return denyList.has(name);
         }
 
         /**
@@ -172,8 +175,8 @@ module.exports = {
 
             /*
              * Member access has special rules for checking property names.
-             * Read access to a property with a blacklisted name is allowed, because it can be on an object that user has no control over.
-             * Write access isn't allowed, because it potentially creates a new property with a blacklisted name.
+             * Read access to a property with a restricted name is allowed, because it can be on an object that user has no control over.
+             * Write access isn't allowed, because it potentially creates a new property with a restricted name.
              */
             if (
                 parent.type === "MemberExpression" &&
@@ -205,7 +208,7 @@ module.exports = {
             if (!reportedNodes.has(node)) {
                 context.report({
                     node,
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: {
                         name: node.name
                     }
@@ -221,7 +224,7 @@ module.exports = {
             },
 
             Identifier(node) {
-                if (isBlacklisted(node.name) && shouldCheck(node)) {
+                if (isRestricted(node.name) && shouldCheck(node)) {
                     report(node);
                 }
             }
diff --git a/eslint/lib/rules/id-denylist.js b/eslint/lib/rules/id-denylist.js
new file mode 100644 (file)
index 0000000..112fd8a
--- /dev/null
@@ -0,0 +1,230 @@
+/**
+ * @fileoverview Rule that warns when identifier names that are
+ * specified in the configuration are used.
+ * @author Keith Cirkel (http://keithcirkel.co.uk)
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether the given node represents assignment target in a normal assignment or destructuring.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is assignment target.
+ */
+function isAssignmentTarget(node) {
+    const parent = node.parent;
+
+    return (
+
+        // normal assignment
+        (
+            parent.type === "AssignmentExpression" &&
+            parent.left === node
+        ) ||
+
+        // destructuring
+        parent.type === "ArrayPattern" ||
+        parent.type === "RestElement" ||
+        (
+            parent.type === "Property" &&
+            parent.value === node &&
+            parent.parent.type === "ObjectPattern"
+        ) ||
+        (
+            parent.type === "AssignmentPattern" &&
+            parent.left === node
+        )
+    );
+}
+
+/**
+ * Checks whether the given node represents an imported name that is renamed in the same import/export specifier.
+ *
+ * Examples:
+ * import { a as b } from 'mod'; // node `a` is renamed import
+ * export { a as b } from 'mod'; // node `a` is renamed import
+ * @param {ASTNode} node `Identifier` node to check.
+ * @returns {boolean} `true` if the node is a renamed import.
+ */
+function isRenamedImport(node) {
+    const parent = node.parent;
+
+    return (
+        (
+            parent.type === "ImportSpecifier" &&
+            parent.imported !== parent.local &&
+            parent.imported === node
+        ) ||
+        (
+            parent.type === "ExportSpecifier" &&
+            parent.parent.source && // re-export
+            parent.local !== parent.exported &&
+            parent.local === node
+        )
+    );
+}
+
+/**
+ * Checks whether the given node is a renamed identifier node in an ObjectPattern destructuring.
+ *
+ * Examples:
+ * const { a : b } = foo; // node `a` is renamed node.
+ * @param {ASTNode} node `Identifier` node to check.
+ * @returns {boolean} `true` if the node is a renamed node in an ObjectPattern destructuring.
+ */
+function isRenamedInDestructuring(node) {
+    const parent = node.parent;
+
+    return (
+        (
+            !parent.computed &&
+            parent.type === "Property" &&
+            parent.parent.type === "ObjectPattern" &&
+            parent.value !== node &&
+            parent.key === node
+        )
+    );
+}
+
+/**
+ * Checks whether the given node represents shorthand definition of a property in an object literal.
+ * @param {ASTNode} node `Identifier` node to check.
+ * @returns {boolean} `true` if the node is a shorthand property definition.
+ */
+function isShorthandPropertyDefinition(node) {
+    const parent = node.parent;
+
+    return (
+        parent.type === "Property" &&
+        parent.parent.type === "ObjectExpression" &&
+        parent.shorthand
+    );
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description: "disallow specified identifiers",
+            category: "Stylistic Issues",
+            recommended: false,
+            url: "https://eslint.org/docs/rules/id-denylist"
+        },
+
+        schema: {
+            type: "array",
+            items: {
+                type: "string"
+            },
+            uniqueItems: true
+        },
+        messages: {
+            restricted: "Identifier '{{name}}' is restricted."
+        }
+    },
+
+    create(context) {
+
+        const denyList = new Set(context.options);
+        const reportedNodes = new Set();
+
+        let globalScope;
+
+        /**
+         * Checks whether the given name is restricted.
+         * @param {string} name The name to check.
+         * @returns {boolean} `true` if the name is restricted.
+         * @private
+         */
+        function isRestricted(name) {
+            return denyList.has(name);
+        }
+
+        /**
+         * Checks whether the given node represents a reference to a global variable that is not declared in the source code.
+         * These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables.
+         * @param {ASTNode} node `Identifier` node to check.
+         * @returns {boolean} `true` if the node is a reference to a global variable.
+         */
+        function isReferenceToGlobalVariable(node) {
+            const variable = globalScope.set.get(node.name);
+
+            return variable && variable.defs.length === 0 &&
+                variable.references.some(ref => ref.identifier === node);
+        }
+
+        /**
+         * Determines whether the given node should be checked.
+         * @param {ASTNode} node `Identifier` node.
+         * @returns {boolean} `true` if the node should be checked.
+         */
+        function shouldCheck(node) {
+            const parent = node.parent;
+
+            /*
+             * Member access has special rules for checking property names.
+             * Read access to a property with a restricted name is allowed, because it can be on an object that user has no control over.
+             * Write access isn't allowed, because it potentially creates a new property with a restricted name.
+             */
+            if (
+                parent.type === "MemberExpression" &&
+                parent.property === node &&
+                !parent.computed
+            ) {
+                return isAssignmentTarget(parent);
+            }
+
+            return (
+                parent.type !== "CallExpression" &&
+                parent.type !== "NewExpression" &&
+                !isRenamedImport(node) &&
+                !isRenamedInDestructuring(node) &&
+                !(
+                    isReferenceToGlobalVariable(node) &&
+                    !isShorthandPropertyDefinition(node)
+                )
+            );
+        }
+
+        /**
+         * Reports an AST node as a rule violation.
+         * @param {ASTNode} node The node to report.
+         * @returns {void}
+         * @private
+         */
+        function report(node) {
+            if (!reportedNodes.has(node)) {
+                context.report({
+                    node,
+                    messageId: "restricted",
+                    data: {
+                        name: node.name
+                    }
+                });
+                reportedNodes.add(node);
+            }
+        }
+
+        return {
+
+            Program() {
+                globalScope = context.getScope();
+            },
+
+            Identifier(node) {
+                if (isRestricted(node.name) && shouldCheck(node)) {
+                    report(node);
+                }
+            }
+        };
+    }
+};
index a68873ac06289b0b3c28b67a04d0ee064c2756c4..4df081ff9fe4f85011848c324784ce8090246d67 100644 (file)
@@ -39,6 +39,13 @@ module.exports = {
                             type: "string"
                         }
                     },
+                    exceptionPatterns: {
+                        type: "array",
+                        uniqueItems: true,
+                        items: {
+                            type: "string"
+                        }
+                    },
                     properties: {
                         enum: ["always", "never"]
                     }
@@ -57,14 +64,20 @@ module.exports = {
         const minLength = typeof options.min !== "undefined" ? options.min : 2;
         const maxLength = typeof options.max !== "undefined" ? options.max : Infinity;
         const properties = options.properties !== "never";
-        const exceptions = (options.exceptions ? options.exceptions : [])
-            .reduce((obj, item) => {
-                obj[item] = true;
-
-                return obj;
-            }, {});
+        const exceptions = new Set(options.exceptions);
+        const exceptionPatterns = (options.exceptionPatterns || []).map(pattern => new RegExp(pattern, "u"));
         const reportedNode = new Set();
 
+        /**
+         * Checks if a string matches the provided exception patterns
+         * @param {string} name The string to check.
+         * @returns {boolean} if the string is a match
+         * @private
+         */
+        function matchesExceptionPattern(name) {
+            return exceptionPatterns.some(pattern => pattern.test(name));
+        }
+
         const SUPPORTED_EXPRESSIONS = {
             MemberExpression: properties && function(parent) {
                 return !parent.computed && (
@@ -112,7 +125,7 @@ module.exports = {
                 const isShort = name.length < minLength;
                 const isLong = name.length > maxLength;
 
-                if (!(isShort || isLong) || exceptions[name]) {
+                if (!(isShort || isLong) || exceptions.has(name) || matchesExceptionPattern(name)) {
                     return; // Nothing to report
                 }
 
index b97a497fd4f8848d4e791bdf717d4a51b4dd4079..7e400d037a07ab39ee0cb970f0706db3de1298a4 100644 (file)
@@ -39,7 +39,8 @@ module.exports = {
                         type: "boolean",
                         default: false
                     }
-                }
+                },
+                additionalProperties: false
             }
         ],
         messages: {
index d576fde038246122f5bfd18fea8ec3b33e3d9fc7..1c0dccc5c9891f3e0d7a82ce20c9713a6680cc3d 100644 (file)
@@ -32,6 +32,7 @@ const KNOWN_NODES = new Set([
     "BreakStatement",
     "CallExpression",
     "CatchClause",
+    "ChainExpression",
     "ClassBody",
     "ClassDeclaration",
     "ClassExpression",
@@ -934,6 +935,24 @@ module.exports = {
             parameterParens.add(openingParen);
             parameterParens.add(closingParen);
 
+            /*
+             * If `?.` token exists, set desired offset for that.
+             * This logic is copied from `MemberExpression`'s.
+             */
+            if (node.optional) {
+                const dotToken = sourceCode.getTokenAfter(node.callee, astUtils.isQuestionDotToken);
+                const calleeParenCount = sourceCode.getTokensBetween(node.callee, dotToken, { filter: astUtils.isClosingParenToken }).length;
+                const firstTokenOfCallee = calleeParenCount
+                    ? sourceCode.getTokenBefore(node.callee, { skip: calleeParenCount - 1 })
+                    : sourceCode.getFirstToken(node.callee);
+                const lastTokenOfCallee = sourceCode.getTokenBefore(dotToken);
+                const offsetBase = lastTokenOfCallee.loc.end.line === openingParen.loc.start.line
+                    ? lastTokenOfCallee
+                    : firstTokenOfCallee;
+
+                offsets.setDesiredOffset(dotToken, offsetBase, 1);
+            }
+
             const offsetAfterToken = node.callee.type === "TaggedTemplateExpression" ? sourceCode.getFirstToken(node.callee.quasi) : openingParen;
             const offsetToken = sourceCode.getTokenBefore(offsetAfterToken);
 
@@ -1065,16 +1084,17 @@ module.exports = {
             },
 
             ArrowFunctionExpression(node) {
-                const firstToken = sourceCode.getFirstToken(node);
+                const maybeOpeningParen = sourceCode.getFirstToken(node, { skip: node.async ? 1 : 0 });
 
-                if (astUtils.isOpeningParenToken(firstToken)) {
-                    const openingParen = firstToken;
+                if (astUtils.isOpeningParenToken(maybeOpeningParen)) {
+                    const openingParen = maybeOpeningParen;
                     const closingParen = sourceCode.getTokenBefore(node.body, astUtils.isClosingParenToken);
 
                     parameterParens.add(openingParen);
                     parameterParens.add(closingParen);
                     addElementListIndent(node.params, openingParen, closingParen, options.FunctionExpression.parameters);
                 }
+
                 addBlocklessNodeIndent(node.body);
             },
 
index 9e5571dd97d4cca4b07af50b8e11c31b574719cf..3cf26e51bc8392ea075fb3ec7be7a9365b778510 100644 (file)
@@ -57,6 +57,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
     "guard-for-in": () => require("./guard-for-in"),
     "handle-callback-err": () => require("./handle-callback-err"),
     "id-blacklist": () => require("./id-blacklist"),
+    "id-denylist": () => require("./id-denylist"),
     "id-length": () => require("./id-length"),
     "id-match": () => require("./id-match"),
     "implicit-arrow-linebreak": () => require("./implicit-arrow-linebreak"),
@@ -176,6 +177,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
     "no-plusplus": () => require("./no-plusplus"),
     "no-process-env": () => require("./no-process-env"),
     "no-process-exit": () => require("./no-process-exit"),
+    "no-promise-executor-return": () => require("./no-promise-executor-return"),
     "no-proto": () => require("./no-proto"),
     "no-prototype-builtins": () => require("./no-prototype-builtins"),
     "no-redeclare": () => require("./no-redeclare"),
@@ -212,6 +214,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({
     "no-unmodified-loop-condition": () => require("./no-unmodified-loop-condition"),
     "no-unneeded-ternary": () => require("./no-unneeded-ternary"),
     "no-unreachable": () => require("./no-unreachable"),
+    "no-unreachable-loop": () => require("./no-unreachable-loop"),
     "no-unsafe-finally": () => require("./no-unsafe-finally"),
     "no-unsafe-negation": () => require("./no-unsafe-negation"),
     "no-unused-expressions": () => require("./no-unused-expressions"),
index 57abb00b06e8a4e5df64b2a34f03ef85e0231c7e..fc885a117a1ea95c881ab733bc41bfb918cadf4d 100644 (file)
@@ -433,11 +433,15 @@ module.exports = {
                 tokenBeforeColon = sourceCode.getTokenBefore(nextColon, { includeComments: true }),
                 tokenAfterColon = sourceCode.getTokenAfter(nextColon, { includeComments: true }),
                 isKeySide = side === "key",
-                locStart = isKeySide ? tokenBeforeColon.loc.start : tokenAfterColon.loc.start,
                 isExtra = diff > 0,
                 diffAbs = Math.abs(diff),
                 spaces = Array(diffAbs + 1).join(" ");
 
+            const locStart = isKeySide ? tokenBeforeColon.loc.end : nextColon.loc.start;
+            const locEnd = isKeySide ? nextColon.loc.start : tokenAfterColon.loc.start;
+            const missingLoc = isKeySide ? tokenBeforeColon.loc : tokenAfterColon.loc;
+            const loc = isExtra ? { start: locStart, end: locEnd } : missingLoc;
+
             if ((
                 diff && mode === "strict" ||
                 diff < 0 && mode === "minimum" ||
@@ -482,7 +486,7 @@ module.exports = {
 
                 context.report({
                     node: property[side],
-                    loc: locStart,
+                    loc,
                     messageId,
                     data: {
                         computed: property.computed ? "computed " : "",
index 99979a32a5bbf7a3354f883494ad7bffd780a627..913cf4682f903acde73b0f1075a96cbd395cdbce 100644 (file)
@@ -126,7 +126,7 @@ module.exports = {
                 !sourceCode.isSpaceBetweenTokens(prevToken, token)
             ) {
                 context.report({
-                    loc: token.loc.start,
+                    loc: token.loc,
                     messageId: "expectedBefore",
                     data: token,
                     fix(fixer) {
@@ -178,7 +178,7 @@ module.exports = {
                 !sourceCode.isSpaceBetweenTokens(token, nextToken)
             ) {
                 context.report({
-                    loc: token.loc.start,
+                    loc: token.loc,
                     messageId: "expectedAfter",
                     data: token,
                     fix(fixer) {
index 995e0c52026385a39ecf4a2287bc705e3907542b..dd76760c50504eb4dc1fd13bc8ce74bfa88a40f8 100644 (file)
@@ -383,11 +383,22 @@ module.exports = {
                     return;
                 }
 
+                const loc = {
+                    start: {
+                        line: lineNumber,
+                        column: 0
+                    },
+                    end: {
+                        line: lineNumber,
+                        column: textToMeasure.length
+                    }
+                };
+
                 if (commentLengthApplies) {
                     if (lineLength > maxCommentLength) {
                         context.report({
                             node,
-                            loc: { line: lineNumber, column: 0 },
+                            loc,
                             messageId: "maxComment",
                             data: {
                                 lineLength,
@@ -398,7 +409,7 @@ module.exports = {
                 } else if (lineLength > maxLength) {
                     context.report({
                         node,
-                        loc: { line: lineNumber, column: 0 },
+                        loc,
                         messageId: "max",
                         data: {
                             lineLength,
index 299377bc2dddfca2ae75ebcee5b0020ad1df4a64..0c7e761fa613b4b3400a8a7b5f62bb7cc94c41a6 100644 (file)
@@ -53,7 +53,8 @@ module.exports = {
             }
         ],
         messages: {
-            exceed: "File has too many lines ({{actual}}). Maximum allowed is {{max}}."
+            exceed:
+                "File has too many lines ({{actual}}). Maximum allowed is {{max}}."
         }
     },
 
@@ -61,7 +62,10 @@ module.exports = {
         const option = context.options[0];
         let max = 300;
 
-        if (typeof option === "object" && Object.prototype.hasOwnProperty.call(option, "max")) {
+        if (
+            typeof option === "object" &&
+            Object.prototype.hasOwnProperty.call(option, "max")
+        ) {
             max = option.max;
         } else if (typeof option === "number") {
             max = option;
@@ -94,7 +98,9 @@ module.exports = {
 
             token = comment;
             do {
-                token = sourceCode.getTokenBefore(token, { includeComments: true });
+                token = sourceCode.getTokenBefore(token, {
+                    includeComments: true
+                });
             } while (isCommentNodeType(token));
 
             if (token && astUtils.isTokenOnSameLine(token, comment)) {
@@ -103,7 +109,9 @@ module.exports = {
 
             token = comment;
             do {
-                token = sourceCode.getTokenAfter(token, { includeComments: true });
+                token = sourceCode.getTokenAfter(token, {
+                    includeComments: true
+                });
             } while (isCommentNodeType(token));
 
             if (token && astUtils.isTokenOnSameLine(comment, token)) {
@@ -118,7 +126,18 @@ module.exports = {
 
         return {
             "Program:exit"() {
-                let lines = sourceCode.lines.map((text, i) => ({ lineNumber: i + 1, text }));
+                let lines = sourceCode.lines.map((text, i) => ({
+                    lineNumber: i + 1,
+                    text
+                }));
+
+                /*
+                 * If file ends with a linebreak, `sourceCode.lines` will have one extra empty line at the end.
+                 * That isn't a real line, so we shouldn't count it.
+                 */
+                if (lines.length > 1 && lodash.last(lines).text === "") {
+                    lines.pop();
+                }
 
                 if (skipBlankLines) {
                     lines = lines.filter(l => l.text.trim() !== "");
@@ -127,14 +146,29 @@ module.exports = {
                 if (skipComments) {
                     const comments = sourceCode.getAllComments();
 
-                    const commentLines = lodash.flatten(comments.map(comment => getLinesWithoutCode(comment)));
+                    const commentLines = lodash.flatten(
+                        comments.map(comment => getLinesWithoutCode(comment))
+                    );
 
-                    lines = lines.filter(l => !lodash.includes(commentLines, l.lineNumber));
+                    lines = lines.filter(
+                        l => !lodash.includes(commentLines, l.lineNumber)
+                    );
                 }
 
                 if (lines.length > max) {
+                    const loc = {
+                        start: {
+                            line: lines[max].lineNumber,
+                            column: 0
+                        },
+                        end: {
+                            line: sourceCode.lines.length,
+                            column: lodash.last(sourceCode.lines).length
+                        }
+                    };
+
                     context.report({
-                        loc: { line: 1, column: 0 },
+                        loc,
                         messageId: "exceed",
                         data: {
                             max,
index 0faf45efb92daf6f1137a962bdd426fd37a44768..4249a542802de14dbc3f613ac328983542943687 100644 (file)
@@ -158,15 +158,9 @@ module.exports = {
          * @returns {string} name
          */
         function extractNameFromExpression(node) {
-
-            let name = "";
-
-            if (node.callee.type === "MemberExpression") {
-                name = astUtils.getStaticPropertyName(node.callee) || "";
-            } else {
-                name = node.callee.name;
-            }
-            return name;
+            return node.callee.type === "Identifier"
+                ? node.callee.name
+                : astUtils.getStaticPropertyName(node.callee) || "";
         }
 
         /**
@@ -212,14 +206,16 @@ module.exports = {
                 return true;
             }
 
-            if (calleeName === "UTC" && node.callee.type === "MemberExpression") {
+            const callee = astUtils.skipChainExpression(node.callee);
+
+            if (calleeName === "UTC" && callee.type === "MemberExpression") {
 
                 // allow if callee is Date.UTC
-                return node.callee.object.type === "Identifier" &&
-                    node.callee.object.name === "Date";
+                return callee.object.type === "Identifier" &&
+                    callee.object.name === "Date";
             }
 
-            return skipProperties && node.callee.type === "MemberExpression";
+            return skipProperties && callee.type === "MemberExpression";
         }
 
         /**
@@ -229,7 +225,7 @@ module.exports = {
          * @returns {void}
          */
         function report(node, messageId) {
-            let callee = node.callee;
+            let callee = astUtils.skipChainExpression(node.callee);
 
             if (callee.type === "MemberExpression") {
                 callee = callee.property;
index 4254fec185ef885113f9192727f43ebe44da3edc..46c9d6c10f802d2b225d6285ea23764417da2576 100644 (file)
@@ -57,7 +57,16 @@ module.exports = {
          * @returns {string} The prefix of the node.
          */
         function getPrefix(node) {
-            return node.computed ? "[" : ".";
+            if (node.computed) {
+                if (node.optional) {
+                    return "?.[";
+                }
+                return "[";
+            }
+            if (node.optional) {
+                return "?.";
+            }
+            return ".";
         }
 
         /**
@@ -76,17 +85,18 @@ module.exports = {
 
         return {
             "CallExpression:exit"(node) {
-                if (!node.callee || node.callee.type !== "MemberExpression") {
+                const callee = astUtils.skipChainExpression(node.callee);
+
+                if (callee.type !== "MemberExpression") {
                     return;
                 }
 
-                const callee = node.callee;
-                let parent = callee.object;
+                let parent = astUtils.skipChainExpression(callee.object);
                 let depth = 1;
 
                 while (parent && parent.callee) {
                     depth += 1;
-                    parent = parent.callee.object;
+                    parent = astUtils.skipChainExpression(astUtils.skipChainExpression(parent.callee).object);
                 }
 
                 if (depth > ignoreChainWithDepth && astUtils.isTokenOnSameLine(callee.object, callee.property)) {
index 22d0dd57bdd82d24c77df486757ebb59c153bffa..702b4d2ba7cccf1ddea02bc10b4168534f0bc7e0 100644 (file)
@@ -10,7 +10,8 @@
 
 const {
     getStaticPropertyName: getPropertyName,
-    getVariableByName
+    getVariableByName,
+    skipChainExpression
 } = require("./utils/ast-utils");
 
 //------------------------------------------------------------------------------
@@ -64,7 +65,13 @@ function isGlobalThisReferenceOrGlobalWindow(scope, node) {
     if (scope.type === "global" && node.type === "ThisExpression") {
         return true;
     }
-    if (node.name === "window" || (node.name === "globalThis" && getVariableByName(scope, "globalThis"))) {
+    if (
+        node.type === "Identifier" &&
+        (
+            node.name === "window" ||
+            (node.name === "globalThis" && getVariableByName(scope, "globalThis"))
+        )
+    ) {
         return !isShadowed(scope, node);
     }
 
@@ -96,7 +103,7 @@ module.exports = {
     create(context) {
         return {
             CallExpression(node) {
-                const callee = node.callee,
+                const callee = skipChainExpression(node.callee),
                     currentScope = context.getScope();
 
                 // without window.
index 5e5838641028fe58c894ac07210f53a89f369a78..790d5ea88111b743a966374e16dad6231aeafc9b 100644 (file)
@@ -9,9 +9,6 @@
 // Helpers
 //------------------------------------------------------------------------------
 
-const EQUALITY_OPERATORS = ["===", "!==", "==", "!="];
-const RELATIONAL_OPERATORS = [">", "<", ">=", "<=", "in", "instanceof"];
-
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
@@ -56,6 +53,35 @@ module.exports = {
         // Helpers
         //--------------------------------------------------------------------------
 
+        /**
+         * Returns literal's value converted to the Boolean type
+         * @param {ASTNode} node any `Literal` node
+         * @returns {boolean | null} `true` when node is truthy, `false` when node is falsy,
+         *  `null` when it cannot be determined.
+         */
+        function getBooleanValue(node) {
+            if (node.value === null) {
+
+                /*
+                 * it might be a null literal or bigint/regex literal in unsupported environments .
+                 * https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es5.md#regexpliteral
+                 * https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es2020.md#bigintliteral
+                 */
+
+                if (node.raw === "null") {
+                    return false;
+                }
+
+                // regex is always truthy
+                if (typeof node.regex === "object") {
+                    return true;
+                }
+
+                return null;
+            }
+
+            return !!node.value;
+        }
 
         /**
          * Checks if a branch node of LogicalExpression short circuits the whole condition
@@ -66,15 +92,23 @@ module.exports = {
         function isLogicalIdentity(node, operator) {
             switch (node.type) {
                 case "Literal":
-                    return (operator === "||" && node.value === true) ||
-                           (operator === "&&" && node.value === false);
+                    return (operator === "||" && getBooleanValue(node) === true) ||
+                           (operator === "&&" && getBooleanValue(node) === false);
 
                 case "UnaryExpression":
                     return (operator === "&&" && node.operator === "void");
 
                 case "LogicalExpression":
-                    return isLogicalIdentity(node.left, node.operator) ||
-                             isLogicalIdentity(node.right, node.operator);
+
+                    /*
+                     * handles `a && false || b`
+                     * `false` is an identity element of `&&` but not `||`
+                     */
+                    return operator === node.operator &&
+                             (
+                                 isLogicalIdentity(node.left, node.operator) ||
+                                 isLogicalIdentity(node.right, node.operator)
+                             );
 
                 // no default
             }
@@ -129,21 +163,9 @@ module.exports = {
                     const isLeftConstant = isConstant(node.left, inBooleanPosition);
                     const isRightConstant = isConstant(node.right, inBooleanPosition);
                     const isLeftShortCircuit = (isLeftConstant && isLogicalIdentity(node.left, node.operator));
-                    const isRightShortCircuit = (isRightConstant && isLogicalIdentity(node.right, node.operator));
+                    const isRightShortCircuit = (inBooleanPosition && isRightConstant && isLogicalIdentity(node.right, node.operator));
 
                     return (isLeftConstant && isRightConstant) ||
-                        (
-
-                            // in the case of an "OR", we need to know if the right constant value is truthy
-                            node.operator === "||" &&
-                            isRightConstant &&
-                            node.right.value &&
-                            (
-                                !node.parent ||
-                                node.parent.type !== "BinaryExpression" ||
-                                !(EQUALITY_OPERATORS.includes(node.parent.operator) || RELATIONAL_OPERATORS.includes(node.parent.operator))
-                            )
-                        ) ||
                         isLeftShortCircuit ||
                         isRightShortCircuit;
                 }
index c8a0fa9da3c02557799946b9a406761d5e5aa956..e2d9665e7f564d8fced4fbba68fe7def57756f67 100644 (file)
@@ -6,6 +6,12 @@
 
 "use strict";
 
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
@@ -31,18 +37,31 @@ module.exports = {
     create(context) {
         const sourceCode = context.getSourceCode();
 
+        /**
+         * Determines whether the two given nodes are considered to be equal.
+         * @param {ASTNode} a First node.
+         * @param {ASTNode} b Second node.
+         * @returns {boolean} `true` if the nodes are considered to be equal.
+         */
+        function equal(a, b) {
+            if (a.type !== b.type) {
+                return false;
+            }
+
+            return astUtils.equalTokens(a, b, sourceCode);
+        }
         return {
             SwitchStatement(node) {
-                const previousKeys = new Set();
+                const previousTests = [];
 
                 for (const switchCase of node.cases) {
                     if (switchCase.test) {
-                        const key = sourceCode.getText(switchCase.test);
+                        const test = switchCase.test;
 
-                        if (previousKeys.has(key)) {
+                        if (previousTests.some(previousTest => equal(previousTest, test))) {
                             context.report({ node: switchCase, messageId: "unexpected" });
                         } else {
-                            previousKeys.add(key);
+                            previousTests.push(test);
                         }
                     }
                 }
index 811ad4e5d73ddaac148e52c0101a1a6f13d46d11..a020fdee014a201c0ba77adf0a908b40e620892e 100644 (file)
@@ -21,38 +21,6 @@ const candidatesOfGlobalObject = Object.freeze([
     "globalThis"
 ]);
 
-/**
- * Checks a given node is a Identifier node of the specified name.
- * @param {ASTNode} node A node to check.
- * @param {string} name A name to check.
- * @returns {boolean} `true` if the node is a Identifier node of the name.
- */
-function isIdentifier(node, name) {
-    return node.type === "Identifier" && node.name === name;
-}
-
-/**
- * Checks a given node is a Literal node of the specified string value.
- * @param {ASTNode} node A node to check.
- * @param {string} name A name to check.
- * @returns {boolean} `true` if the node is a Literal node of the name.
- */
-function isConstant(node, name) {
-    switch (node.type) {
-        case "Literal":
-            return node.value === name;
-
-        case "TemplateLiteral":
-            return (
-                node.expressions.length === 0 &&
-                node.quasis[0].value.cooked === name
-            );
-
-        default:
-            return false;
-    }
-}
-
 /**
  * Checks a given node is a MemberExpression node which has the specified name's
  * property.
@@ -62,10 +30,7 @@ function isConstant(node, name) {
  *      the specified name's property
  */
 function isMember(node, name) {
-    return (
-        node.type === "MemberExpression" &&
-        (node.computed ? isConstant : isIdentifier)(node.property, name)
-    );
+    return astUtils.isSpecificMemberAccess(node, null, name);
 }
 
 //------------------------------------------------------------------------------
@@ -230,7 +195,12 @@ module.exports = {
                 "CallExpression:exit"(node) {
                     const callee = node.callee;
 
-                    if (isIdentifier(callee, "eval")) {
+                    /*
+                     * Optional call (`eval?.("code")`) is not direct eval.
+                     * The direct eval is only step 6.a.vi of https://tc39.es/ecma262/#sec-function-calls-runtime-semantics-evaluation
+                     * But the optional call is https://tc39.es/ecma262/#sec-optional-chaining-chain-evaluation
+                     */
+                    if (!node.optional && astUtils.isSpecificId(callee, "eval")) {
                         report(callee);
                     }
                 }
@@ -241,7 +211,7 @@ module.exports = {
             "CallExpression:exit"(node) {
                 const callee = node.callee;
 
-                if (isIdentifier(callee, "eval")) {
+                if (astUtils.isSpecificId(callee, "eval")) {
                     report(callee);
                 }
             },
index 7ab25ab4895476004c365a48468ea9328aea354a..db365b50924d7ab80a9cb56d69e1c1040fc9615b 100644 (file)
 const astUtils = require("./utils/ast-utils");
 const globals = require("globals");
 
-//------------------------------------------------------------------------------
-// Helpers
-//------------------------------------------------------------------------------
-
-const propertyDefinitionMethods = new Set(["defineProperty", "defineProperties"]);
-
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
@@ -100,40 +94,30 @@ module.exports = {
         }
 
         /**
-         * Checks that an identifier is an object of a prototype whose member
-         * is being assigned in an AssignmentExpression.
-         * Example: Object.prototype.foo = "bar"
-         * @param {ASTNode} identifierNode The identifier to check.
-         * @returns {boolean} True if the identifier's prototype is modified.
+         * Check if it's an assignment to the property of the given node.
+         * Example: `*.prop = 0` // the `*` is the given node.
+         * @param {ASTNode} node The node to check.
+         * @returns {boolean} True if an assignment to the property of the node.
          */
-        function isInPrototypePropertyAssignment(identifierNode) {
-            return Boolean(
-                isPrototypePropertyAccessed(identifierNode) &&
-                identifierNode.parent.parent.type === "MemberExpression" &&
-                identifierNode.parent.parent.parent.type === "AssignmentExpression" &&
-                identifierNode.parent.parent.parent.left === identifierNode.parent.parent
+        function isAssigningToPropertyOf(node) {
+            return (
+                node.parent.type === "MemberExpression" &&
+                node.parent.object === node &&
+                node.parent.parent.type === "AssignmentExpression" &&
+                node.parent.parent.left === node.parent
             );
         }
 
         /**
-         * Checks that an identifier is an object of a prototype whose member
-         * is being extended via the Object.defineProperty() or
-         * Object.defineProperties() methods.
-         * Example: Object.defineProperty(Array.prototype, "foo", ...)
-         * Example: Object.defineProperties(Array.prototype, ...)
-         * @param {ASTNode} identifierNode The identifier to check.
-         * @returns {boolean} True if the identifier's prototype is modified.
+         * Checks if the given node is at the first argument of the method call of `Object.defineProperty()` or `Object.defineProperties()`.
+         * @param {ASTNode} node The node to check.
+         * @returns {boolean} True if the node is at the first argument of the method call of `Object.defineProperty()` or `Object.defineProperties()`.
          */
-        function isInDefinePropertyCall(identifierNode) {
-            return Boolean(
-                isPrototypePropertyAccessed(identifierNode) &&
-                identifierNode.parent.parent.type === "CallExpression" &&
-                identifierNode.parent.parent.arguments[0] === identifierNode.parent &&
-                identifierNode.parent.parent.callee.type === "MemberExpression" &&
-                identifierNode.parent.parent.callee.object.type === "Identifier" &&
-                identifierNode.parent.parent.callee.object.name === "Object" &&
-                identifierNode.parent.parent.callee.property.type === "Identifier" &&
-                propertyDefinitionMethods.has(identifierNode.parent.parent.callee.property.name)
+        function isInDefinePropertyCall(node) {
+            return (
+                node.parent.type === "CallExpression" &&
+                node.parent.arguments[0] === node &&
+                astUtils.isSpecificMemberAccess(node.parent.callee, "Object", /^definePropert(?:y|ies)$/u)
             );
         }
 
@@ -149,14 +133,27 @@ module.exports = {
          * @returns {void}
          */
         function checkAndReportPrototypeExtension(identifierNode) {
-            if (isInPrototypePropertyAssignment(identifierNode)) {
+            if (!isPrototypePropertyAccessed(identifierNode)) {
+                return; // This is not `*.prototype` access.
+            }
+
+            /*
+             * `identifierNode.parent` is a MamberExpression `*.prototype`.
+             * If it's an optional member access, it may be wrapped by a `ChainExpression` node.
+             */
+            const prototypeNode =
+                identifierNode.parent.parent.type === "ChainExpression"
+                    ? identifierNode.parent.parent
+                    : identifierNode.parent;
+
+            if (isAssigningToPropertyOf(prototypeNode)) {
 
-                // Identifier --> MemberExpression --> MemberExpression --> AssignmentExpression
-                reportNode(identifierNode.parent.parent.parent, identifierNode.name);
-            } else if (isInDefinePropertyCall(identifierNode)) {
+                // `*.prototype` -> MemberExpression -> AssignmentExpression
+                reportNode(prototypeNode.parent.parent, identifierNode.name);
+            } else if (isInDefinePropertyCall(prototypeNode)) {
 
-                // Identifier --> MemberExpression --> CallExpression
-                reportNode(identifierNode.parent.parent, identifierNode.name);
+                // `*.prototype` -> CallExpression
+                reportNode(prototypeNode.parent, identifierNode.name);
             }
         }
 
index df695924ab52999e46c1e126ce63c0b9470fc159..2db440dc1ea9b7beba32f9a05c89c5700e770b0d 100644 (file)
@@ -61,24 +61,62 @@ module.exports = {
          * @returns {void}
          */
         function report(node) {
+            const memberNode = node.parent;
+            const callNode = memberNode.parent.type === "ChainExpression"
+                ? memberNode.parent.parent
+                : memberNode.parent;
+
             context.report({
-                node: node.parent.parent,
+                node: callNode,
                 messageId: "unexpected",
-                loc: node.parent.property.loc,
+                loc: memberNode.property.loc,
+
                 fix(fixer) {
-                    if (node.parent.parent.arguments.length && !isSideEffectFree(node.parent.parent.arguments[0])) {
+                    if (!isSideEffectFree(callNode.arguments[0])) {
                         return null;
                     }
 
-                    const firstTokenToRemove = sourceCode
-                        .getFirstTokenBetween(node.parent.object, node.parent.property, astUtils.isNotClosingParenToken);
-                    const lastTokenToRemove = sourceCode.getLastToken(node.parent.parent);
+                    /*
+                     * The list of the first/last token pair of a removal range.
+                     * This is two parts because closing parentheses may exist between the method name and arguments.
+                     * E.g. `(function(){}.bind ) (obj)`
+                     *                    ^^^^^   ^^^^^ < removal ranges
+                     * E.g. `(function(){}?.['bind'] ) ?.(obj)`
+                     *                    ^^^^^^^^^^   ^^^^^^^ < removal ranges
+                     */
+                    const tokenPairs = [
+                        [
+
+                            // `.`, `?.`, or `[` token.
+                            sourceCode.getTokenAfter(
+                                memberNode.object,
+                                astUtils.isNotClosingParenToken
+                            ),
+
+                            // property name or `]` token.
+                            sourceCode.getLastToken(memberNode)
+                        ],
+                        [
+
+                            // `?.` or `(` token of arguments.
+                            sourceCode.getTokenAfter(
+                                memberNode,
+                                astUtils.isNotClosingParenToken
+                            ),
+
+                            // `)` token of arguments.
+                            sourceCode.getLastToken(callNode)
+                        ]
+                    ];
+                    const firstTokenToRemove = tokenPairs[0][0];
+                    const lastTokenToRemove = tokenPairs[1][1];
 
                     if (sourceCode.commentsExistBetween(firstTokenToRemove, lastTokenToRemove)) {
                         return null;
                     }
 
-                    return fixer.removeRange([firstTokenToRemove.range[0], node.parent.parent.range[1]]);
+                    return tokenPairs.map(([start, end]) =>
+                        fixer.removeRange([start.range[0], end.range[1]]));
                 }
             });
         }
@@ -93,18 +131,20 @@ module.exports = {
          * @returns {boolean} `true` if the node is the callee of `.bind()` method.
          */
         function isCalleeOfBindMethod(node) {
-            const parent = node.parent;
-            const grandparent = parent.parent;
+            if (!astUtils.isSpecificMemberAccess(node.parent, null, "bind")) {
+                return false;
+            }
+
+            // The node of `*.bind` member access.
+            const bindNode = node.parent.parent.type === "ChainExpression"
+                ? node.parent.parent
+                : node.parent;
 
             return (
-                grandparent &&
-                grandparent.type === "CallExpression" &&
-                grandparent.callee === parent &&
-                grandparent.arguments.length === 1 &&
-                grandparent.arguments[0].type !== "SpreadElement" &&
-                parent.type === "MemberExpression" &&
-                parent.object === node &&
-                astUtils.getStaticPropertyName(parent) === "bind"
+                bindNode.parent.type === "CallExpression" &&
+                bindNode.parent.callee === bindNode &&
+                bindNode.parent.arguments.length === 1 &&
+                bindNode.parent.arguments[0].type !== "SpreadElement"
             );
         }
 
index b90757b112658c8bcd8ad869230574ae58bf12f5..6ae3ea62ca77d67b064f113d197e03f137893f1a 100644 (file)
@@ -111,6 +111,10 @@ module.exports = {
          * @returns {boolean} If the node is in one of the flagged contexts
          */
         function isInFlaggedContext(node) {
+            if (node.parent.type === "ChainExpression") {
+                return isInFlaggedContext(node.parent);
+            }
+
             return isInBooleanContext(node) ||
             (isLogicalContext(node.parent) &&
 
@@ -149,6 +153,9 @@ module.exports = {
          * @returns {boolean} `true` if the node needs to be parenthesized.
          */
         function needsParens(previousNode, node) {
+            if (previousNode.parent.type === "ChainExpression") {
+                return needsParens(previousNode.parent, node);
+            }
             if (isParenthesized(previousNode)) {
 
                 // parentheses around the previous node will stay, so there is no need for an additional pair
index bae1a498cf0cf131d35276d9c3f61d284922a63e..7afe7625dfcd1d80fc4bbdd0994726981cdab536 100644 (file)
@@ -100,10 +100,18 @@ module.exports = {
          * @private
          */
         function isImmediateFunctionPrototypeMethodCall(node) {
-            return node.type === "CallExpression" &&
-                node.callee.type === "MemberExpression" &&
-                node.callee.object.type === "FunctionExpression" &&
-                ["call", "apply"].includes(astUtils.getStaticPropertyName(node.callee));
+            const callNode = astUtils.skipChainExpression(node);
+
+            if (callNode.type !== "CallExpression") {
+                return false;
+            }
+            const callee = astUtils.skipChainExpression(callNode.callee);
+
+            return (
+                callee.type === "MemberExpression" &&
+                callee.object.type === "FunctionExpression" &&
+                ["call", "apply"].includes(astUtils.getStaticPropertyName(callee))
+            );
         }
 
         /**
@@ -360,7 +368,9 @@ module.exports = {
          * @returns {boolean} `true` if the given node is an IIFE
          */
         function isIIFE(node) {
-            return node.type === "CallExpression" && node.callee.type === "FunctionExpression";
+            const maybeCallNode = astUtils.skipChainExpression(node);
+
+            return maybeCallNode.type === "CallExpression" && maybeCallNode.callee.type === "FunctionExpression";
         }
 
         /**
@@ -466,13 +476,16 @@ module.exports = {
 
                 if (
                     hasDoubleExcessParens(callee) ||
-                    !isIIFE(node) && !hasNewParensException && !(
+                    !isIIFE(node) &&
+                    !hasNewParensException &&
+                    !(
 
                         // Allow extra parens around a new expression if they are intervening parentheses.
                         node.type === "NewExpression" &&
                         callee.type === "MemberExpression" &&
                         doesMemberExpressionContainCallExpression(callee)
-                    )
+                    ) &&
+                    !(!node.optional && callee.type === "ChainExpression")
                 ) {
                     report(node.callee);
                 }
@@ -710,6 +723,20 @@ module.exports = {
             reportsBuffer.reports = reportsBuffer.reports.filter(r => r.node !== node);
         }
 
+        /**
+         * Checks whether a node is a MemberExpression at NewExpression's callee.
+         * @param {ASTNode} node node to check.
+         * @returns {boolean} True if the node is a MemberExpression at NewExpression's callee. false otherwise.
+         */
+        function isMemberExpInNewCallee(node) {
+            if (node.type === "MemberExpression") {
+                return node.parent.type === "NewExpression" && node.parent.callee === node
+                    ? true
+                    : node.parent.object === node && isMemberExpInNewCallee(node.parent);
+            }
+            return false;
+        }
+
         return {
             ArrayExpression(node) {
                 node.elements
@@ -950,7 +977,11 @@ module.exports = {
             LogicalExpression: checkBinaryLogical,
 
             MemberExpression(node) {
-                const nodeObjHasExcessParens = hasExcessParens(node.object) &&
+                const shouldAllowWrapOnce = isMemberExpInNewCallee(node) &&
+                  doesMemberExpressionContainCallExpression(node);
+                const nodeObjHasExcessParens = shouldAllowWrapOnce
+                    ? hasDoubleExcessParens(node.object)
+                    : hasExcessParens(node.object) &&
                     !(
                         isImmediateFunctionPrototypeMethodCall(node.parent) &&
                         node.parent.callee === node &&
@@ -974,8 +1005,8 @@ module.exports = {
                 }
 
                 if (nodeObjHasExcessParens &&
-                  node.object.type === "CallExpression" &&
-                  node.parent.type !== "NewExpression") {
+                  node.object.type === "CallExpression"
+                ) {
                     report(node.object);
                 }
 
@@ -986,6 +1017,13 @@ module.exports = {
                     report(node.object);
                 }
 
+                if (nodeObjHasExcessParens &&
+                    node.optional &&
+                    node.object.type === "ChainExpression"
+                ) {
+                    report(node.object);
+                }
+
                 if (node.computed && hasExcessParens(node.property)) {
                     report(node.property);
                 }
@@ -1071,7 +1109,22 @@ module.exports = {
             },
 
             UnaryExpression: checkArgumentWithPrecedence,
-            UpdateExpression: checkArgumentWithPrecedence,
+            UpdateExpression(node) {
+                if (node.prefix) {
+                    checkArgumentWithPrecedence(node);
+                } else {
+                    const { argument } = node;
+                    const operatorToken = sourceCode.getLastToken(node);
+
+                    if (argument.loc.end.line === operatorToken.loc.start.line) {
+                        checkArgumentWithPrecedence(node);
+                    } else {
+                        if (hasDoubleExcessParens(argument)) {
+                            report(argument);
+                        }
+                    }
+                }
+            },
             AwaitExpression: checkArgumentWithPrecedence,
 
             VariableDeclarator(node) {
index 6d5ee61e96bd5b74f38bb7f058eefd1d7fa3cecc..a639711ecea1558b7abd2850bc47a951f0ba4355 100644 (file)
@@ -47,12 +47,14 @@ function isDoubleLogicalNegating(node) {
  * @returns {boolean} Whether or not the node is a binary negating of `.indexOf()` method calling.
  */
 function isBinaryNegatingOfIndexOf(node) {
+    if (node.operator !== "~") {
+        return false;
+    }
+    const callNode = astUtils.skipChainExpression(node.argument);
+
     return (
-        node.operator === "~" &&
-        node.argument.type === "CallExpression" &&
-        node.argument.callee.type === "MemberExpression" &&
-        node.argument.callee.property.type === "Identifier" &&
-        INDEX_OF_PATTERN.test(node.argument.callee.property.name)
+        callNode.type === "CallExpression" &&
+        astUtils.isSpecificMemberAccess(callNode.callee, null, INDEX_OF_PATTERN)
     );
 }
 
@@ -246,7 +248,10 @@ module.exports = {
                 // ~foo.indexOf(bar)
                 operatorAllowed = options.allow.indexOf("~") >= 0;
                 if (!operatorAllowed && options.boolean && isBinaryNegatingOfIndexOf(node)) {
-                    const recommendation = `${sourceCode.getText(node.argument)} !== -1`;
+
+                    // `foo?.indexOf(bar) !== -1` will be true (== found) if the `foo` is nullish. So use `>= 0` in that case.
+                    const comparison = node.argument.type === "ChainExpression" ? ">= 0" : "!== -1";
+                    const recommendation = `${sourceCode.getText(node.argument)} ${comparison}`;
 
                     report(node, recommendation, false);
                 }
index 1668a0432a52775132a2664809cce59a552dffd8..b8120a64887c5a38c4b30896ae986c20775ca182 100644 (file)
@@ -35,8 +35,8 @@ module.exports = {
     },
 
     create(context) {
-        const EVAL_LIKE_FUNCS = Object.freeze(["setTimeout", "execScript", "setInterval"]);
         const GLOBAL_CANDIDATES = Object.freeze(["global", "window", "globalThis"]);
+        const EVAL_LIKE_FUNC_PATTERN = /^(?:set(?:Interval|Timeout)|execScript)$/u;
 
         /**
          * Checks whether a node is evaluated as a string or not.
@@ -56,28 +56,6 @@ module.exports = {
             return false;
         }
 
-        /**
-         * Checks whether a node is an Identifier node named one of the specified names.
-         * @param {ASTNode} node A node to check.
-         * @param {string[]} specifiers Array of specified name.
-         * @returns {boolean} True if the node is a Identifier node which has specified name.
-         */
-        function isSpecifiedIdentifier(node, specifiers) {
-            return node.type === "Identifier" && specifiers.includes(node.name);
-        }
-
-        /**
-         * Checks a given node is a MemberExpression node which has the specified name's
-         * property.
-         * @param {ASTNode} node A node to check.
-         * @param {string[]} specifiers Array of specified name.
-         * @returns {boolean} `true` if the node is a MemberExpression node which has
-         *      the specified name's property
-         */
-        function isSpecifiedMember(node, specifiers) {
-            return node.type === "MemberExpression" && specifiers.includes(astUtils.getStaticPropertyName(node));
-        }
-
         /**
          * Reports if the `CallExpression` node has evaluated argument.
          * @param {ASTNode} node A CallExpression to check.
@@ -114,14 +92,15 @@ module.exports = {
                 const identifier = ref.identifier;
                 let node = identifier.parent;
 
-                while (isSpecifiedMember(node, [name])) {
+                while (astUtils.isSpecificMemberAccess(node, null, name)) {
                     node = node.parent;
                 }
 
-                if (isSpecifiedMember(node, EVAL_LIKE_FUNCS)) {
-                    const parent = node.parent;
+                if (astUtils.isSpecificMemberAccess(node, null, EVAL_LIKE_FUNC_PATTERN)) {
+                    const calleeNode = node.parent.type === "ChainExpression" ? node.parent : node;
+                    const parent = calleeNode.parent;
 
-                    if (parent.type === "CallExpression" && parent.callee === node) {
+                    if (parent.type === "CallExpression" && parent.callee === calleeNode) {
                         reportImpliedEvalCallExpression(parent);
                     }
                 }
@@ -134,7 +113,7 @@ module.exports = {
 
         return {
             CallExpression(node) {
-                if (isSpecifiedIdentifier(node.callee, EVAL_LIKE_FUNCS)) {
+                if (astUtils.isSpecificId(node.callee, EVAL_LIKE_FUNC_PATTERN)) {
                     reportImpliedEvalCallExpression(node);
                 }
             },
index 32e445ff68b3efb5279dd8d3b1c755bef95b3b23..7a349bb730bdcda95bb45c6183220eada26aa3fd 100644 (file)
@@ -9,16 +9,12 @@
 // Helpers
 //------------------------------------------------------------------------------
 
-const { findVariable, getPropertyName } = require("eslint-utils");
-
-const MutationMethods = {
-    Object: new Set([
-        "assign", "defineProperties", "defineProperty", "freeze",
-        "setPrototypeOf"
-    ]),
-    Reflect: new Set([
-        "defineProperty", "deleteProperty", "set", "setPrototypeOf"
-    ])
+const { findVariable } = require("eslint-utils");
+const astUtils = require("./utils/ast-utils");
+
+const WellKnownMutationFunctions = {
+    Object: /^(?:assign|definePropert(?:y|ies)|freeze|setPrototypeOf)$/u,
+    Reflect: /^(?:(?:define|delete)Property|set(?:PrototypeOf)?)$/u
 };
 
 /**
@@ -56,17 +52,20 @@ function isAssignmentLeft(node) {
  * @returns {boolean} `true` if the node is the operand of mutation unary operator.
  */
 function isOperandOfMutationUnaryOperator(node) {
-    const { parent } = node;
+    const argumentNode = node.parent.type === "ChainExpression"
+        ? node.parent
+        : node;
+    const { parent } = argumentNode;
 
     return (
         (
             parent.type === "UpdateExpression" &&
-            parent.argument === node
+            parent.argument === argumentNode
         ) ||
         (
             parent.type === "UnaryExpression" &&
             parent.operator === "delete" &&
-            parent.argument === node
+            parent.argument === argumentNode
         )
     );
 }
@@ -92,35 +91,37 @@ function isIterationVariable(node) {
 }
 
 /**
- * Check if a given node is the iteration variable of `for-in`/`for-of` syntax.
+ * Check if a given node is at the first argument of a well-known mutation function.
+ * - `Object.assign`
+ * - `Object.defineProperty`
+ * - `Object.defineProperties`
+ * - `Object.freeze`
+ * - `Object.setPrototypeOf`
+ * - `Refrect.defineProperty`
+ * - `Refrect.deleteProperty`
+ * - `Refrect.set`
+ * - `Refrect.setPrototypeOf`
  * @param {ASTNode} node The node to check.
  * @param {Scope} scope A `escope.Scope` object to find variable (whichever).
- * @returns {boolean} `true` if the node is the iteration variable.
+ * @returns {boolean} `true` if the node is at the first argument of a well-known mutation function.
  */
 function isArgumentOfWellKnownMutationFunction(node, scope) {
     const { parent } = node;
 
+    if (parent.type !== "CallExpression" || parent.arguments[0] !== node) {
+        return false;
+    }
+    const callee = astUtils.skipChainExpression(parent.callee);
+
     if (
-        parent.type === "CallExpression" &&
-        parent.arguments[0] === node &&
-        parent.callee.type === "MemberExpression" &&
-        parent.callee.object.type === "Identifier"
+        !astUtils.isSpecificMemberAccess(callee, "Object", WellKnownMutationFunctions.Object) &&
+        !astUtils.isSpecificMemberAccess(callee, "Reflect", WellKnownMutationFunctions.Reflect)
     ) {
-        const { callee } = parent;
-        const { object } = callee;
-
-        if (Object.keys(MutationMethods).includes(object.name)) {
-            const variable = findVariable(scope, object);
-
-            return (
-                variable !== null &&
-                variable.scope.type === "global" &&
-                MutationMethods[object.name].has(getPropertyName(callee, scope))
-            );
-        }
+        return false;
     }
+    const variable = findVariable(scope, callee.object);
 
-    return false;
+    return variable !== null && variable.scope.type === "global";
 }
 
 /**
index 41b0f1e664c776f844938c11437457ae356e9ac8..dec278615e2e120344b366884e820b01592d22d1 100644 (file)
@@ -21,7 +21,17 @@ module.exports = {
             url: "https://eslint.org/docs/rules/no-inline-comments"
         },
 
-        schema: [],
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    ignorePattern: {
+                        type: "string"
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
 
         messages: {
             unexpectedInlineComment: "Unexpected comment inline with code."
@@ -30,6 +40,12 @@ module.exports = {
 
     create(context) {
         const sourceCode = context.getSourceCode();
+        const options = context.options[0];
+        let customIgnoreRegExp;
+
+        if (options && options.ignorePattern) {
+            customIgnoreRegExp = new RegExp(options.ignorePattern, "u");
+        }
 
         /**
          * Will check that comments are not on lines starting with or ending with code
@@ -51,6 +67,11 @@ module.exports = {
                 return;
             }
 
+            // Matches the ignore pattern
+            if (customIgnoreRegExp && customIgnoreRegExp.test(node.value)) {
+                return;
+            }
+
             // JSX Exception
             if (
                 (isPreambleEmpty || preamble === "{") &&
@@ -80,9 +101,9 @@ module.exports = {
 
         return {
             Program() {
-                const comments = sourceCode.getAllComments();
-
-                comments.filter(token => token.type !== "Shebang").forEach(testCodeAroundComment);
+                sourceCode.getAllComments()
+                    .filter(token => token.type !== "Shebang")
+                    .forEach(testCodeAroundComment);
             }
         };
     }
index 21842331f21ff04891ee9e56202d918184d340da..0bf69b128e6bf80ee9413e2fe4a320232ba4877a 100644 (file)
@@ -91,7 +91,7 @@ module.exports = {
             const locStart = node.loc.start;
             const locEnd = node.loc.end;
 
-            errors = errors.filter(({ loc: errorLoc }) => {
+            errors = errors.filter(({ loc: { start: errorLoc } }) => {
                 if (errorLoc.line >= locStart.line && errorLoc.line <= locEnd.line) {
                     if (errorLoc.column >= locStart.column && (errorLoc.column <= locEnd.column || errorLoc.line < locEnd.line)) {
                         return false;
@@ -160,15 +160,19 @@ module.exports = {
                 let match;
 
                 while ((match = IRREGULAR_WHITESPACE.exec(sourceLine)) !== null) {
-                    const location = {
-                        line: lineNumber,
-                        column: match.index
-                    };
-
                     errors.push({
                         node,
                         messageId: "noIrregularWhitespace",
-                        loc: location
+                        loc: {
+                            start: {
+                                line: lineNumber,
+                                column: match.index
+                            },
+                            end: {
+                                line: lineNumber,
+                                column: match.index + match[0].length
+                            }
+                        }
                     });
                 }
             });
@@ -189,16 +193,22 @@ module.exports = {
 
             while ((match = IRREGULAR_LINE_TERMINATORS.exec(source)) !== null) {
                 const lineIndex = linebreaks.indexOf(match[0], lastLineIndex + 1) || 0;
-                const location = {
-                    line: lineIndex + 1,
-                    column: sourceLines[lineIndex].length
-                };
 
                 errors.push({
                     node,
                     messageId: "noIrregularWhitespace",
-                    loc: location
+                    loc: {
+                        start: {
+                            line: lineIndex + 1,
+                            column: sourceLines[lineIndex].length
+                        },
+                        end: {
+                            line: lineIndex + 2,
+                            column: 0
+                        }
+                    }
                 });
+
                 lastLineIndex = lineIndex;
             }
         }
index b95677c2e0b272e3cc94b35db01e603e4e760528..9e2811809f68e788526de04076cc2df9ea744e72 100644 (file)
@@ -36,6 +36,14 @@ module.exports = {
             return typeof node.value === "number";
         }
 
+        /**
+         * Gets the source code of the given number literal. Removes `_` numeric separators from the result.
+         * @param {Node} node the number `Literal` node
+         * @returns {string} raw source code of the literal, without numeric separators
+         */
+        function getRaw(node) {
+            return node.raw.replace(/_/gu, "");
+        }
 
         /**
          * Checks whether the number is  base ten
@@ -55,7 +63,7 @@ module.exports = {
          * @returns {boolean} true if they do not match
          */
         function notBaseTenLosesPrecision(node) {
-            const rawString = node.raw.toUpperCase();
+            const rawString = getRaw(node).toUpperCase();
             let base = 0;
 
             if (rawString.startsWith("0B")) {
@@ -161,7 +169,7 @@ module.exports = {
          * @returns {boolean} true if they do not match
          */
         function baseTenLosesPrecision(node) {
-            const normalizedRawNumber = convertNumberToScientificNotation(node.raw);
+            const normalizedRawNumber = convertNumberToScientificNotation(getRaw(node));
             const requestedPrecision = normalizedRawNumber.split("e")[0].replace(".", "").length;
 
             if (requestedPrecision > 100) {
index cd07f5c3bda9d54e68bd2fe710cd7fc878973a90..510b3f9b26172ec7617d4a0312bc32bc2251707e 100644 (file)
@@ -5,7 +5,7 @@
 
 "use strict";
 
-const { isNumericLiteral } = require("./utils/ast-utils");
+const astUtils = require("./utils/ast-utils");
 
 // Maximum array length by the ECMAScript Specification.
 const MAX_ARRAY_LENGTH = 2 ** 32 - 1;
@@ -61,6 +61,10 @@ module.exports = {
                 ignoreArrayIndexes: {
                     type: "boolean",
                     default: false
+                },
+                ignoreDefaultValues: {
+                    type: "boolean",
+                    default: false
                 }
             },
             additionalProperties: false
@@ -77,7 +81,8 @@ module.exports = {
             detectObjects = !!config.detectObjects,
             enforceConst = !!config.enforceConst,
             ignore = (config.ignore || []).map(normalizeIgnoreValue),
-            ignoreArrayIndexes = !!config.ignoreArrayIndexes;
+            ignoreArrayIndexes = !!config.ignoreArrayIndexes,
+            ignoreDefaultValues = !!config.ignoreDefaultValues;
 
         const okTypes = detectObjects ? [] : ["ObjectExpression", "Property", "AssignmentExpression"];
 
@@ -90,6 +95,17 @@ module.exports = {
             return ignore.indexOf(value) !== -1;
         }
 
+        /**
+         * Returns whether the number is a default value assignment.
+         * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
+         * @returns {boolean} true if the number is a default value
+         */
+        function isDefaultValue(fullNumberNode) {
+            const parent = fullNumberNode.parent;
+
+            return parent.type === "AssignmentPattern" && parent.right === fullNumberNode;
+        }
+
         /**
          * Returns whether the given node is used as a radix within parseInt() or Number.parseInt()
          * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node
@@ -100,12 +116,8 @@ module.exports = {
 
             return parent.type === "CallExpression" && fullNumberNode === parent.arguments[1] &&
                 (
-                    parent.callee.name === "parseInt" ||
-                    (
-                        parent.callee.type === "MemberExpression" &&
-                        parent.callee.object.name === "Number" &&
-                        parent.callee.property.name === "parseInt"
-                    )
+                    astUtils.isSpecificId(parent.callee, "parseInt") ||
+                    astUtils.isSpecificMemberAccess(parent.callee, "Number", "parseInt")
                 );
         }
 
@@ -157,7 +169,7 @@ module.exports = {
 
         return {
             Literal(node) {
-                if (!isNumericLiteral(node)) {
+                if (!astUtils.isNumericLiteral(node)) {
                     return;
                 }
 
@@ -176,9 +188,12 @@ module.exports = {
                     raw = node.raw;
                 }
 
+                const parent = fullNumberNode.parent;
+
                 // Always allow radix arguments and JSX props
                 if (
                     isIgnoredValue(value) ||
+                    (ignoreDefaultValues && isDefaultValue(fullNumberNode)) ||
                     isParseIntRadix(fullNumberNode) ||
                     isJSXNumber(fullNumberNode) ||
                     (ignoreArrayIndexes && isArrayIndex(fullNumberNode, value))
@@ -186,8 +201,6 @@ module.exports = {
                     return;
                 }
 
-                const parent = fullNumberNode.parent;
-
                 if (parent.type === "VariableDeclarator") {
                     if (enforceConst && parent.parent.kind !== "const") {
                         context.report({
index 6139ba2c182b8b96ae451b92a998b91fb66fc52e..6eb200c9b879e626fda1d3d9edf14d1b62dea660 100644 (file)
@@ -24,10 +24,13 @@ const nonCallableGlobals = ["Atomics", "JSON", "Math", "Reflect"];
  * @returns {string} name to report
  */
 function getReportNodeName(node) {
-    if (node.callee.type === "MemberExpression") {
-        return getPropertyName(node.callee);
+    if (node.type === "ChainExpression") {
+        return getReportNodeName(node.expression);
     }
-    return node.callee.name;
+    if (node.type === "MemberExpression") {
+        return getPropertyName(node);
+    }
+    return node.name;
 }
 
 //------------------------------------------------------------------------------
@@ -69,7 +72,7 @@ module.exports = {
                 }
 
                 for (const { node, path } of tracker.iterateGlobalReferences(traceMap)) {
-                    const name = getReportNodeName(node);
+                    const name = getReportNodeName(node.callee);
                     const ref = path[0];
                     const messageId = name === ref ? "unexpectedCall" : "unexpectedRefCall";
 
diff --git a/eslint/lib/rules/no-promise-executor-return.js b/eslint/lib/rules/no-promise-executor-return.js
new file mode 100644 (file)
index 0000000..32ee6e1
--- /dev/null
@@ -0,0 +1,121 @@
+/**
+ * @fileoverview Rule to disallow returning values from Promise executor functions
+ * @author Milos Djermanovic
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const { findVariable } = require("eslint-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const functionTypesToCheck = new Set(["ArrowFunctionExpression", "FunctionExpression"]);
+
+/**
+ * Determines whether the given identifier node is a reference to a global variable.
+ * @param {ASTNode} node `Identifier` node to check.
+ * @param {Scope} scope Scope to which the node belongs.
+ * @returns {boolean} True if the identifier is a reference to a global variable.
+ */
+function isGlobalReference(node, scope) {
+    const variable = findVariable(scope, node);
+
+    return variable !== null && variable.scope.type === "global" && variable.defs.length === 0;
+}
+
+/**
+ * Finds function's outer scope.
+ * @param {Scope} scope Function's own scope.
+ * @returns {Scope} Function's outer scope.
+ */
+function getOuterScope(scope) {
+    const upper = scope.upper;
+
+    if (upper.type === "function-expression-name") {
+        return upper.upper;
+    }
+    return upper;
+}
+
+/**
+ * Determines whether the given function node is used as a Promise executor.
+ * @param {ASTNode} node The node to check.
+ * @param {Scope} scope Function's own scope.
+ * @returns {boolean} `true` if the node is a Promise executor.
+ */
+function isPromiseExecutor(node, scope) {
+    const parent = node.parent;
+
+    return parent.type === "NewExpression" &&
+        parent.arguments[0] === node &&
+        parent.callee.type === "Identifier" &&
+        parent.callee.name === "Promise" &&
+        isGlobalReference(parent.callee, getOuterScope(scope));
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "disallow returning values from Promise executor functions",
+            category: "Possible Errors",
+            recommended: false,
+            url: "https://eslint.org/docs/rules/no-promise-executor-return"
+        },
+
+        schema: [],
+
+        messages: {
+            returnsValue: "Return values from promise executor functions cannot be read."
+        }
+    },
+
+    create(context) {
+
+        let funcInfo = null;
+
+        /**
+         * Reports the given node.
+         * @param {ASTNode} node Node to report.
+         * @returns {void}
+         */
+        function report(node) {
+            context.report({ node, messageId: "returnsValue" });
+        }
+
+        return {
+
+            onCodePathStart(_, node) {
+                funcInfo = {
+                    upper: funcInfo,
+                    shouldCheck: functionTypesToCheck.has(node.type) && isPromiseExecutor(node, context.getScope())
+                };
+
+                if (funcInfo.shouldCheck && node.type === "ArrowFunctionExpression" && node.expression) {
+                    report(node.body);
+                }
+            },
+
+            onCodePathEnd() {
+                funcInfo = funcInfo.upper;
+            },
+
+            ReturnStatement(node) {
+                if (funcInfo.shouldCheck && node.argument) {
+                    report(node);
+                }
+            }
+        };
+    }
+};
index a00d3707204ffddc4a078e15394a28361ede2c20..c5e4d49259b84adbc41ecd9412d9f2eb7016714b 100644 (file)
@@ -4,6 +4,12 @@
  */
 "use strict";
 
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
@@ -39,15 +45,19 @@ module.exports = {
          * @returns {void}
          */
         function disallowBuiltIns(node) {
-            if (node.callee.type !== "MemberExpression" || node.callee.computed) {
+
+            const callee = astUtils.skipChainExpression(node.callee);
+
+            if (callee.type !== "MemberExpression") {
                 return;
             }
-            const propName = node.callee.property.name;
 
-            if (DISALLOWED_PROPS.indexOf(propName) > -1) {
+            const propName = astUtils.getStaticPropertyName(callee);
+
+            if (propName !== null && DISALLOWED_PROPS.indexOf(propName) > -1) {
                 context.report({
                     messageId: "prototypeBuildIn",
-                    loc: node.callee.property.loc,
+                    loc: callee.property.loc,
                     data: { prop: propName },
                     node
                 });
index 2078fc1dcea122a685562c4140c2ca25e7f18e4c..0c82052440372f7c8890dd3ae891929ff1a2df08 100644 (file)
@@ -7,6 +7,8 @@
 
 "use strict";
 
+const astUtils = require("./utils/ast-utils");
+
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
@@ -31,18 +33,30 @@ module.exports = {
 
     create(context) {
 
+        /**
+         * Check whether a node's static value starts with "javascript:" or not.
+         * And report an error for unexpected script URL.
+         * @param {ASTNode} node node to check
+         * @returns {void}
+         */
+        function check(node) {
+            const value = astUtils.getStaticStringValue(node);
+
+            if (typeof value === "string" && value.toLowerCase().indexOf("javascript:") === 0) {
+                context.report({ node, messageId: "unexpectedScriptURL" });
+            }
+        }
         return {
-
             Literal(node) {
                 if (node.value && typeof node.value === "string") {
-                    const value = node.value.toLowerCase();
-
-                    if (value.indexOf("javascript:") === 0) {
-                        context.report({ node, messageId: "unexpectedScriptURL" });
-                    }
+                    check(node);
+                }
+            },
+            TemplateLiteral(node) {
+                if (!(node.parent && node.parent.type === "TaggedTemplateExpression")) {
+                    check(node);
                 }
             }
         };
-
     }
 };
index 170e46b05930d12410d9d9086e22d88b9afbccbb..705be324cf0944967eea6d0b16f72b3468a65d57 100644 (file)
@@ -17,56 +17,6 @@ const astUtils = require("./utils/ast-utils");
 
 const SPACES = /\s+/gu;
 
-/**
- * Checks whether the property of 2 given member expression nodes are the same
- * property or not.
- * @param {ASTNode} left A member expression node to check.
- * @param {ASTNode} right Another member expression node to check.
- * @returns {boolean} `true` if the member expressions have the same property.
- */
-function isSameProperty(left, right) {
-    if (left.property.type === "Identifier" &&
-        left.property.type === right.property.type &&
-        left.property.name === right.property.name &&
-        left.computed === right.computed
-    ) {
-        return true;
-    }
-
-    const lname = astUtils.getStaticPropertyName(left);
-    const rname = astUtils.getStaticPropertyName(right);
-
-    return lname !== null && lname === rname;
-}
-
-/**
- * Checks whether 2 given member expression nodes are the reference to the same
- * property or not.
- * @param {ASTNode} left A member expression node to check.
- * @param {ASTNode} right Another member expression node to check.
- * @returns {boolean} `true` if the member expressions are the reference to the
- *  same property or not.
- */
-function isSameMember(left, right) {
-    if (!isSameProperty(left, right)) {
-        return false;
-    }
-
-    const lobj = left.object;
-    const robj = right.object;
-
-    if (lobj.type !== robj.type) {
-        return false;
-    }
-    if (lobj.type === "MemberExpression") {
-        return isSameMember(lobj, robj);
-    }
-    if (lobj.type === "ThisExpression") {
-        return true;
-    }
-    return lobj.type === "Identifier" && lobj.name === robj.name;
-}
-
 /**
  * Traverses 2 Pattern nodes in parallel, then reports self-assignments.
  * @param {ASTNode|null} left A left node to traverse. This is a Pattern or
@@ -162,9 +112,9 @@ function eachSelfAssignment(left, right, props, report) {
         }
     } else if (
         props &&
-        left.type === "MemberExpression" &&
-        right.type === "MemberExpression" &&
-        isSameMember(left, right)
+        astUtils.skipChainExpression(left).type === "MemberExpression" &&
+        astUtils.skipChainExpression(right).type === "MemberExpression" &&
+        astUtils.isSameReference(left, right)
     ) {
         report(right);
     }
index a558640c357d9210f221cc58795f9a90658c4850..9c79240dda1fa8f2cf962f79be6737cbf2103a44 100644 (file)
@@ -39,15 +39,12 @@ function isGlobalReference(node, scope) {
  * @returns {boolean} `true` if the node is argument at the given position.
  */
 function isArgumentOfGlobalMethodCall(node, scope, objectName, methodName, index) {
-    const parent = node.parent;
+    const callNode = node.parent;
 
-    return parent.type === "CallExpression" &&
-        parent.arguments[index] === node &&
-        parent.callee.type === "MemberExpression" &&
-        astUtils.getStaticPropertyName(parent.callee) === methodName &&
-        parent.callee.object.type === "Identifier" &&
-        parent.callee.object.name === objectName &&
-        isGlobalReference(parent.callee.object, scope);
+    return callNode.type === "CallExpression" &&
+        callNode.arguments[index] === node &&
+        astUtils.isSpecificMemberAccess(callNode.callee, objectName, methodName) &&
+        isGlobalReference(astUtils.skipChainExpression(callNode.callee).object, scope);
 }
 
 /**
index cac594e10047e84b9e0bc067ae238f79de535967..87d2336fa4a40f38f96bd9ea4775cf5a809ccaf6 100644 (file)
@@ -1,5 +1,5 @@
 /**
- * @fileoverview Rule to flag trailing underscores in variable declarations.
+ * @fileoverview Rule to flag dangling underscores in variable declarations.
  * @author Matt DuVall <http://www.mattduvall.com>
  */
 
@@ -45,6 +45,10 @@ module.exports = {
                     enforceInMethodNames: {
                         type: "boolean",
                         default: false
+                    },
+                    allowFunctionParams: {
+                        type: "boolean",
+                        default: true
                     }
                 },
                 additionalProperties: false
@@ -64,6 +68,7 @@ module.exports = {
         const allowAfterSuper = typeof options.allowAfterSuper !== "undefined" ? options.allowAfterSuper : false;
         const allowAfterThisConstructor = typeof options.allowAfterThisConstructor !== "undefined" ? options.allowAfterThisConstructor : false;
         const enforceInMethodNames = typeof options.enforceInMethodNames !== "undefined" ? options.enforceInMethodNames : false;
+        const allowFunctionParams = typeof options.allowFunctionParams !== "undefined" ? options.allowFunctionParams : true;
 
         //-------------------------------------------------------------------------
         // Helpers
@@ -80,12 +85,12 @@ module.exports = {
         }
 
         /**
-         * Check if identifier has a underscore at the end
+         * Check if identifier has a dangling underscore
          * @param {string} identifier name of the node
          * @returns {boolean} true if its is present
          * @private
          */
-        function hasTrailingUnderscore(identifier) {
+        function hasDanglingUnderscore(identifier) {
             const len = identifier.length;
 
             return identifier !== "_" && (identifier[0] === "_" || identifier[len - 1] === "_");
@@ -126,16 +131,53 @@ module.exports = {
         }
 
         /**
-         * Check if function has a underscore at the end
+         * Check if function parameter has a dangling underscore.
+         * @param {ASTNode} node function node to evaluate
+         * @returns {void}
+         * @private
+         */
+        function checkForDanglingUnderscoreInFunctionParameters(node) {
+            if (!allowFunctionParams) {
+                node.params.forEach(param => {
+                    const { type } = param;
+                    let nodeToCheck;
+
+                    if (type === "RestElement") {
+                        nodeToCheck = param.argument;
+                    } else if (type === "AssignmentPattern") {
+                        nodeToCheck = param.left;
+                    } else {
+                        nodeToCheck = param;
+                    }
+
+                    if (nodeToCheck.type === "Identifier") {
+                        const identifier = nodeToCheck.name;
+
+                        if (hasDanglingUnderscore(identifier) && !isAllowed(identifier)) {
+                            context.report({
+                                node: param,
+                                messageId: "unexpectedUnderscore",
+                                data: {
+                                    identifier
+                                }
+                            });
+                        }
+                    }
+                });
+            }
+        }
+
+        /**
+         * Check if function has a dangling underscore
          * @param {ASTNode} node node to evaluate
          * @returns {void}
          * @private
          */
-        function checkForTrailingUnderscoreInFunctionDeclaration(node) {
-            if (node.id) {
+        function checkForDanglingUnderscoreInFunction(node) {
+            if (node.type === "FunctionDeclaration" && node.id) {
                 const identifier = node.id.name;
 
-                if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) && !isAllowed(identifier)) {
+                if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) && !isAllowed(identifier)) {
                     context.report({
                         node,
                         messageId: "unexpectedUnderscore",
@@ -145,18 +187,19 @@ module.exports = {
                     });
                 }
             }
+            checkForDanglingUnderscoreInFunctionParameters(node);
         }
 
         /**
-         * Check if variable expression has a underscore at the end
+         * Check if variable expression has a dangling underscore
          * @param {ASTNode} node node to evaluate
          * @returns {void}
          * @private
          */
-        function checkForTrailingUnderscoreInVariableExpression(node) {
+        function checkForDanglingUnderscoreInVariableExpression(node) {
             const identifier = node.id.name;
 
-            if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) &&
+            if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) &&
                 !isSpecialCaseIdentifierInVariableExpression(identifier) && !isAllowed(identifier)) {
                 context.report({
                     node,
@@ -169,18 +212,18 @@ module.exports = {
         }
 
         /**
-         * Check if member expression has a underscore at the end
+         * Check if member expression has a dangling underscore
          * @param {ASTNode} node node to evaluate
          * @returns {void}
          * @private
          */
-        function checkForTrailingUnderscoreInMemberExpression(node) {
+        function checkForDanglingUnderscoreInMemberExpression(node) {
             const identifier = node.property.name,
                 isMemberOfThis = node.object.type === "ThisExpression",
                 isMemberOfSuper = node.object.type === "Super",
                 isMemberOfThisConstructor = isThisConstructorReference(node);
 
-            if (typeof identifier !== "undefined" && hasTrailingUnderscore(identifier) &&
+            if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) &&
                 !(isMemberOfThis && allowAfterThis) &&
                 !(isMemberOfSuper && allowAfterSuper) &&
                 !(isMemberOfThisConstructor && allowAfterThisConstructor) &&
@@ -196,16 +239,16 @@ module.exports = {
         }
 
         /**
-         * Check if method declaration or method property has a underscore at the end
+         * Check if method declaration or method property has a dangling underscore
          * @param {ASTNode} node node to evaluate
          * @returns {void}
          * @private
          */
-        function checkForTrailingUnderscoreInMethod(node) {
+        function checkForDanglingUnderscoreInMethod(node) {
             const identifier = node.key.name;
             const isMethod = node.type === "MethodDefinition" || node.type === "Property" && node.method;
 
-            if (typeof identifier !== "undefined" && enforceInMethodNames && isMethod && hasTrailingUnderscore(identifier) && !isAllowed(identifier)) {
+            if (typeof identifier !== "undefined" && enforceInMethodNames && isMethod && hasDanglingUnderscore(identifier) && !isAllowed(identifier)) {
                 context.report({
                     node,
                     messageId: "unexpectedUnderscore",
@@ -221,11 +264,13 @@ module.exports = {
         //--------------------------------------------------------------------------
 
         return {
-            FunctionDeclaration: checkForTrailingUnderscoreInFunctionDeclaration,
-            VariableDeclarator: checkForTrailingUnderscoreInVariableExpression,
-            MemberExpression: checkForTrailingUnderscoreInMemberExpression,
-            MethodDefinition: checkForTrailingUnderscoreInMethod,
-            Property: checkForTrailingUnderscoreInMethod
+            FunctionDeclaration: checkForDanglingUnderscoreInFunction,
+            VariableDeclarator: checkForDanglingUnderscoreInVariableExpression,
+            MemberExpression: checkForDanglingUnderscoreInMemberExpression,
+            MethodDefinition: checkForDanglingUnderscoreInMethod,
+            Property: checkForDanglingUnderscoreInMethod,
+            FunctionExpression: checkForDanglingUnderscoreInFunction,
+            ArrowFunctionExpression: checkForDanglingUnderscoreInFunction
         };
 
     }
index b5ec20de4b20a34cd2bbf9bedecab5650f2592a4..7af3fe67090dfa23e0136e1597cb8c225cfac35c 100644 (file)
@@ -68,7 +68,7 @@ module.exports = {
         return {
 
             MemberExpression(node) {
-                if (!node.computed) {
+                if (!node.computed || node.optional) {
                     return;
                 }
                 checkForBreakAfter(node.object, "property");
@@ -96,7 +96,7 @@ module.exports = {
             },
 
             CallExpression(node) {
-                if (node.arguments.length === 0) {
+                if (node.arguments.length === 0 || node.optional) {
                     return;
                 }
                 checkForBreakAfter(node.callee, "function");
index 0fefc42b909820ff1feeee2fe9e95a573591c99f..06c615f38240502304f9d821ee3ab795a88431c0 100644 (file)
@@ -122,7 +122,6 @@ module.exports = {
                 if (isBooleanLiteral(node.alternate) && isBooleanLiteral(node.consequent)) {
                     context.report({
                         node,
-                        loc: node.consequent.loc.start,
                         messageId: "unnecessaryConditionalExpression",
                         fix(fixer) {
                             if (node.consequent.value === node.alternate.value) {
@@ -144,7 +143,6 @@ module.exports = {
                 } else if (!defaultAssignment && matchesDefaultAssignment(node)) {
                     context.report({
                         node,
-                        loc: node.consequent.loc.start,
                         messageId: "unnecessaryConditionalAssignment",
                         fix: fixer => {
                             const shouldParenthesizeAlternate =
diff --git a/eslint/lib/rules/no-unreachable-loop.js b/eslint/lib/rules/no-unreachable-loop.js
new file mode 100644 (file)
index 0000000..868a6ff
--- /dev/null
@@ -0,0 +1,150 @@
+/**
+ * @fileoverview Rule to disallow loops with a body that allows only one iteration
+ * @author Milos Djermanovic
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const allLoopTypes = ["WhileStatement", "DoWhileStatement", "ForStatement", "ForInStatement", "ForOfStatement"];
+
+/**
+ * Determines whether the given node is the first node in the code path to which a loop statement
+ * 'loops' for the next iteration.
+ * @param {ASTNode} node The node to check.
+ * @returns {boolean} `true` if the node is a looping target.
+ */
+function isLoopingTarget(node) {
+    const parent = node.parent;
+
+    if (parent) {
+        switch (parent.type) {
+            case "WhileStatement":
+                return node === parent.test;
+            case "DoWhileStatement":
+                return node === parent.body;
+            case "ForStatement":
+                return node === (parent.update || parent.test || parent.body);
+            case "ForInStatement":
+            case "ForOfStatement":
+                return node === parent.left;
+
+            // no default
+        }
+    }
+
+    return false;
+}
+
+/**
+ * Creates an array with elements from the first given array that are not included in the second given array.
+ * @param {Array} arrA The array to compare from.
+ * @param {Array} arrB The array to compare against.
+ * @returns {Array} a new array that represents `arrA \ arrB`.
+ */
+function getDifference(arrA, arrB) {
+    return arrA.filter(a => !arrB.includes(a));
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+    meta: {
+        type: "problem",
+
+        docs: {
+            description: "disallow loops with a body that allows only one iteration",
+            category: "Possible Errors",
+            recommended: false,
+            url: "https://eslint.org/docs/rules/no-unreachable-loop"
+        },
+
+        schema: [{
+            type: "object",
+            properties: {
+                ignore: {
+                    type: "array",
+                    items: {
+                        enum: allLoopTypes
+                    },
+                    uniqueItems: true
+                }
+            },
+            additionalProperties: false
+        }],
+
+        messages: {
+            invalid: "Invalid loop. Its body allows only one iteration."
+        }
+    },
+
+    create(context) {
+        const ignoredLoopTypes = context.options[0] && context.options[0].ignore || [],
+            loopTypesToCheck = getDifference(allLoopTypes, ignoredLoopTypes),
+            loopSelector = loopTypesToCheck.join(","),
+            loopsByTargetSegments = new Map(),
+            loopsToReport = new Set();
+
+        let currentCodePath = null;
+
+        return {
+            onCodePathStart(codePath) {
+                currentCodePath = codePath;
+            },
+
+            onCodePathEnd() {
+                currentCodePath = currentCodePath.upper;
+            },
+
+            [loopSelector](node) {
+
+                /**
+                 * Ignore unreachable loop statements to avoid unnecessary complexity in the implementation, or false positives otherwise.
+                 * For unreachable segments, the code path analysis does not raise events required for this implementation.
+                 */
+                if (currentCodePath.currentSegments.some(segment => segment.reachable)) {
+                    loopsToReport.add(node);
+                }
+            },
+
+            onCodePathSegmentStart(segment, node) {
+                if (isLoopingTarget(node)) {
+                    const loop = node.parent;
+
+                    loopsByTargetSegments.set(segment, loop);
+                }
+            },
+
+            onCodePathSegmentLoop(_, toSegment, node) {
+                const loop = loopsByTargetSegments.get(toSegment);
+
+                /**
+                 * The second iteration is reachable, meaning that the loop is valid by the logic of this rule,
+                 * only if there is at least one loop event with the appropriate target (which has been already
+                 * determined in the `loopsByTargetSegments` map), raised from either:
+                 *
+                 * - the end of the loop's body (in which case `node === loop`)
+                 * - a `continue` statement
+                 *
+                 * This condition skips loop events raised from `ForInStatement > .right` and `ForOfStatement > .right` nodes.
+                 */
+                if (node === loop || node.type === "ContinueStatement") {
+
+                    // Removes loop if it exists in the set. Otherwise, `Set#delete` has no effect and doesn't throw.
+                    loopsToReport.delete(loop);
+                }
+            },
+
+            "Program:exit"() {
+                loopsToReport.forEach(
+                    node => context.report({ node, messageId: "invalid" })
+                );
+            }
+        };
+    }
+};
index 8c049f556ff2d923607472b2a04c825ee3877151..882a0fd1c11276c9baa3769344ea5f117e9af358 100644 (file)
@@ -8,6 +8,22 @@
 // Rule Definition
 //------------------------------------------------------------------------------
 
+/**
+ * Returns `true`.
+ * @returns {boolean} `true`.
+ */
+function alwaysTrue() {
+    return true;
+}
+
+/**
+ * Returns `false`.
+ * @returns {boolean} `false`.
+ */
+function alwaysFalse() {
+    return false;
+}
+
 module.exports = {
     meta: {
         type: "suggestion",
@@ -101,40 +117,56 @@ module.exports = {
         }
 
         /**
-         * Determines whether or not a given node is a valid expression. Recurses on short circuit eval and ternary nodes if enabled by flags.
-         * @param {ASTNode} node any node
-         * @returns {boolean} whether the given node is a valid expression
+         * The member functions return `true` if the type has no side-effects.
+         * Unknown nodes are handled as `false`, then this rule ignores those.
          */
-        function isValidExpression(node) {
-            if (allowTernary) {
-
-                // Recursive check for ternary and logical expressions
-                if (node.type === "ConditionalExpression") {
-                    return isValidExpression(node.consequent) && isValidExpression(node.alternate);
+        const Checker = Object.assign(Object.create(null), {
+            isDisallowed(node) {
+                return (Checker[node.type] || alwaysFalse)(node);
+            },
+
+            ArrayExpression: alwaysTrue,
+            ArrowFunctionExpression: alwaysTrue,
+            BinaryExpression: alwaysTrue,
+            ChainExpression(node) {
+                return Checker.isDisallowed(node.expression);
+            },
+            ClassExpression: alwaysTrue,
+            ConditionalExpression(node) {
+                if (allowTernary) {
+                    return Checker.isDisallowed(node.consequent) || Checker.isDisallowed(node.alternate);
                 }
-            }
-
-            if (allowShortCircuit) {
-                if (node.type === "LogicalExpression") {
-                    return isValidExpression(node.right);
+                return true;
+            },
+            FunctionExpression: alwaysTrue,
+            Identifier: alwaysTrue,
+            Literal: alwaysTrue,
+            LogicalExpression(node) {
+                if (allowShortCircuit) {
+                    return Checker.isDisallowed(node.right);
                 }
-            }
-
-            if (allowTaggedTemplates && node.type === "TaggedTemplateExpression") {
                 return true;
+            },
+            MemberExpression: alwaysTrue,
+            MetaProperty: alwaysTrue,
+            ObjectExpression: alwaysTrue,
+            SequenceExpression: alwaysTrue,
+            TaggedTemplateExpression() {
+                return !allowTaggedTemplates;
+            },
+            TemplateLiteral: alwaysTrue,
+            ThisExpression: alwaysTrue,
+            UnaryExpression(node) {
+                return node.operator !== "void" && node.operator !== "delete";
             }
-
-            return /^(?:Assignment|Call|New|Update|Yield|Await|Import)Expression$/u.test(node.type) ||
-                (node.type === "UnaryExpression" && ["delete", "void"].indexOf(node.operator) >= 0);
-        }
+        });
 
         return {
             ExpressionStatement(node) {
-                if (!isValidExpression(node.expression) && !isDirective(node, context.getAncestors())) {
+                if (Checker.isDisallowed(node.expression) && !isDirective(node, context.getAncestors())) {
                     context.report({ node, messageId: "unusedExpression" });
                 }
             }
         };
-
     }
 };
index 50dcab45fe2bb65e5afece01fcf763df7d3e03c3..4dc6dc2bab34bc074264772921694a6c0572c370 100644 (file)
@@ -68,7 +68,8 @@ module.exports = {
                             caughtErrorsIgnorePattern: {
                                 type: "string"
                             }
-                        }
+                        },
+                        additionalProperties: false
                     }
                 ]
             }
index afc729d5de0d9b3e2c673cd3774b6affa935fc8f..b1382a2fa28034588e48b77b136b81a617275ef7 100644 (file)
@@ -17,13 +17,15 @@ const astUtils = require("./utils/ast-utils");
  * @returns {boolean} Whether or not the node is a `.call()`/`.apply()`.
  */
 function isCallOrNonVariadicApply(node) {
+    const callee = astUtils.skipChainExpression(node.callee);
+
     return (
-        node.callee.type === "MemberExpression" &&
-        node.callee.property.type === "Identifier" &&
-        node.callee.computed === false &&
+        callee.type === "MemberExpression" &&
+        callee.property.type === "Identifier" &&
+        callee.computed === false &&
         (
-            (node.callee.property.name === "call" && node.arguments.length >= 1) ||
-            (node.callee.property.name === "apply" && node.arguments.length === 2 && node.arguments[1].type === "ArrayExpression")
+            (callee.property.name === "call" && node.arguments.length >= 1) ||
+            (callee.property.name === "apply" && node.arguments.length === 2 && node.arguments[1].type === "ArrayExpression")
         )
     );
 }
@@ -74,12 +76,13 @@ module.exports = {
                     return;
                 }
 
-                const applied = node.callee.object;
+                const callee = astUtils.skipChainExpression(node.callee);
+                const applied = astUtils.skipChainExpression(callee.object);
                 const expectedThis = (applied.type === "MemberExpression") ? applied.object : null;
                 const thisArg = node.arguments[0];
 
                 if (isValidThisArg(expectedThis, thisArg, sourceCode)) {
-                    context.report({ node, messageId: "unnecessaryCall", data: { name: node.callee.property.name } });
+                    context.report({ node, messageId: "unnecessaryCall", data: { name: callee.property.name } });
                 }
             }
         };
index d70bd5dd5c400261b652ddbcd67bffffb6099fe5..0691a31f77e429bcd6ce69a6f9995b452a873b39 100644 (file)
@@ -8,6 +8,8 @@
 const { escapeRegExp } = require("lodash");
 const astUtils = require("./utils/ast-utils");
 
+const CHAR_LIMIT = 40;
+
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
@@ -42,12 +44,11 @@ module.exports = {
         ],
 
         messages: {
-            unexpectedComment: "Unexpected '{{matchedTerm}}' comment."
+            unexpectedComment: "Unexpected '{{matchedTerm}}' comment: '{{comment}}'."
         }
     },
 
     create(context) {
-
         const sourceCode = context.getSourceCode(),
             configuration = context.options[0] || {},
             warningTerms = configuration.terms || ["todo", "fixme", "xxx"],
@@ -107,7 +108,15 @@ module.exports = {
              * \bTERM\b|\bTERM\b, this checks the entire comment
              * for the term.
              */
-            return new RegExp(prefix + escaped + suffix + eitherOrWordBoundary + term + wordBoundary, "iu");
+            return new RegExp(
+                prefix +
+                    escaped +
+                    suffix +
+                    eitherOrWordBoundary +
+                    term +
+                    wordBoundary,
+                "iu"
+            );
         }
 
         const warningRegExps = warningTerms.map(convertToRegExp);
@@ -135,18 +144,40 @@ module.exports = {
          * @returns {void} undefined.
          */
         function checkComment(node) {
-            if (astUtils.isDirectiveComment(node) && selfConfigRegEx.test(node.value)) {
+            const comment = node.value;
+
+            if (
+                astUtils.isDirectiveComment(node) &&
+                selfConfigRegEx.test(comment)
+            ) {
                 return;
             }
 
-            const matches = commentContainsWarningTerm(node.value);
+            const matches = commentContainsWarningTerm(comment);
 
             matches.forEach(matchedTerm => {
+                let commentToDisplay = "";
+                let truncated = false;
+
+                for (const c of comment.trim().split(/\s+/u)) {
+                    const tmp = commentToDisplay ? `${commentToDisplay} ${c}` : c;
+
+                    if (tmp.length <= CHAR_LIMIT) {
+                        commentToDisplay = tmp;
+                    } else {
+                        truncated = true;
+                        break;
+                    }
+                }
+
                 context.report({
                     node,
                     messageId: "unexpectedComment",
                     data: {
-                        matchedTerm
+                        matchedTerm,
+                        comment: `${commentToDisplay}${
+                            truncated ? "..." : ""
+                        }`
                     }
                 });
             });
@@ -156,7 +187,9 @@ module.exports = {
             Program() {
                 const comments = sourceCode.getAllComments();
 
-                comments.filter(token => token.type !== "Shebang").forEach(checkComment);
+                comments
+                    .filter(token => token.type !== "Shebang")
+                    .forEach(checkComment);
             }
         };
     }
index ccd0b091b744e3ed668c61c01231a8c5d2f2a23f..226f873c5f6bd7d50b40fff1286ff794f6b07a2a 100644 (file)
@@ -49,8 +49,6 @@ module.exports = {
          * @private
          */
         function reportError(node, leftToken, rightToken) {
-            const replacementText = node.computed ? "" : ".";
-
             context.report({
                 node,
                 messageId: "unexpectedWhitespace",
@@ -58,7 +56,9 @@ module.exports = {
                     propName: sourceCode.getText(node.property)
                 },
                 fix(fixer) {
-                    if (!node.computed && astUtils.isDecimalInteger(node.object)) {
+                    let replacementText = "";
+
+                    if (!node.computed && !node.optional && astUtils.isDecimalInteger(node.object)) {
 
                         /*
                          * If the object is a number literal, fixing it to something like 5.toString() would cause a SyntaxError.
@@ -66,6 +66,18 @@ module.exports = {
                          */
                         return null;
                     }
+
+                    // Don't fix if comments exist.
+                    if (sourceCode.commentsExistBetween(leftToken, rightToken)) {
+                        return null;
+                    }
+
+                    if (node.optional) {
+                        replacementText = "?.";
+                    } else if (!node.computed) {
+                        replacementText = ".";
+                    }
+
                     return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], replacementText);
                 }
             });
@@ -86,7 +98,7 @@ module.exports = {
 
                 if (node.computed) {
                     rightToken = sourceCode.getTokenBefore(node.property, astUtils.isOpeningBracketToken);
-                    leftToken = sourceCode.getTokenBefore(rightToken);
+                    leftToken = sourceCode.getTokenBefore(rightToken, node.optional ? 1 : 0);
                 } else {
                     rightToken = sourceCode.getFirstToken(node.property);
                     leftToken = sourceCode.getTokenBefore(rightToken, 1);
index b48b2526a0b2bd0d68eaf33bdc54df80a263e40c..616d59851d646d5085bbc77a58a61101dced609c 100644 (file)
@@ -224,7 +224,7 @@ module.exports = {
                     context.report({
                         messageId: "expectedLinebreakAfterOpeningBrace",
                         node,
-                        loc: openBrace.loc.start,
+                        loc: openBrace.loc,
                         fix(fixer) {
                             if (hasCommentsFirstToken) {
                                 return null;
@@ -238,7 +238,7 @@ module.exports = {
                     context.report({
                         messageId: "expectedLinebreakBeforeClosingBrace",
                         node,
-                        loc: closeBrace.loc.start,
+                        loc: closeBrace.loc,
                         fix(fixer) {
                             if (hasCommentsLastToken) {
                                 return null;
@@ -260,7 +260,7 @@ module.exports = {
                     context.report({
                         messageId: "unexpectedLinebreakAfterOpeningBrace",
                         node,
-                        loc: openBrace.loc.start,
+                        loc: openBrace.loc,
                         fix(fixer) {
                             if (hasCommentsFirstToken) {
                                 return null;
@@ -280,7 +280,7 @@ module.exports = {
                     context.report({
                         messageId: "unexpectedLinebreakBeforeClosingBrace",
                         node,
-                        loc: closeBrace.loc.start,
+                        loc: closeBrace.loc,
                         fix(fixer) {
                             if (hasCommentsLastToken) {
                                 return null;
index 074bc775ae35a3401f15208b509fb9dde4e0ded2..0c7f800282e57ae3b3d405dd32a09e3696b81442 100644 (file)
@@ -77,7 +77,7 @@ module.exports = {
                     if (lastTokenOfPreviousProperty.loc.end.line === firstTokenOfCurrentProperty.loc.start.line) {
                         context.report({
                             node,
-                            loc: firstTokenOfCurrentProperty.loc.start,
+                            loc: firstTokenOfCurrentProperty.loc,
                             messageId,
                             fix(fixer) {
                                 const comma = sourceCode.getTokenBefore(firstTokenOfCurrentProperty);
index 6820793439cb84a7a4fb1e51eec3746093af4718..fdb0884922b6548d58ab8c11c6d3465ab0d8fd7c 100644 (file)
@@ -40,45 +40,6 @@ function isNonCommutativeOperatorWithShorthand(operator) {
 // Rule Definition
 //------------------------------------------------------------------------------
 
-/**
- * Checks whether two expressions reference the same value. For example:
- *     a = a
- *     a.b = a.b
- *     a[0] = a[0]
- *     a['b'] = a['b']
- * @param   {ASTNode} a Left side of the comparison.
- * @param   {ASTNode} b Right side of the comparison.
- * @returns {boolean}   True if both sides match and reference the same value.
- */
-function same(a, b) {
-    if (a.type !== b.type) {
-        return false;
-    }
-
-    switch (a.type) {
-        case "Identifier":
-            return a.name === b.name;
-
-        case "Literal":
-            return a.value === b.value;
-
-        case "MemberExpression":
-
-            /*
-             * x[0] = x[0]
-             * x[y] = x[y]
-             * x.y = x.y
-             */
-            return same(a.object, b.object) && same(a.property, b.property);
-
-        case "ThisExpression":
-            return true;
-
-        default:
-            return false;
-    }
-}
-
 /**
  * Determines if the left side of a node can be safely fixed (i.e. if it activates the same getters/setters and)
  * toString calls regardless of whether assignment shorthand is used)
@@ -148,12 +109,12 @@ module.exports = {
             const operator = expr.operator;
 
             if (isCommutativeOperatorWithShorthand(operator) || isNonCommutativeOperatorWithShorthand(operator)) {
-                if (same(left, expr.left)) {
+                if (astUtils.isSameReference(left, expr.left, true)) {
                     context.report({
                         node,
                         messageId: "replaced",
                         fix(fixer) {
-                            if (canBeFixed(left)) {
+                            if (canBeFixed(left) && canBeFixed(expr.left)) {
                                 const equalsToken = getOperatorToken(node);
                                 const operatorToken = getOperatorToken(expr);
                                 const leftText = sourceCode.getText().slice(node.range[0], equalsToken.range[0]);
@@ -169,7 +130,7 @@ module.exports = {
                             return null;
                         }
                     });
-                } else if (same(left, expr.right) && isCommutativeOperatorWithShorthand(operator)) {
+                } else if (astUtils.isSameReference(left, expr.right, true) && isCommutativeOperatorWithShorthand(operator)) {
 
                     /*
                      * This case can't be fixed safely.
@@ -190,7 +151,7 @@ module.exports = {
          * @returns {void}
          */
         function prohibit(node) {
-            if (node.operator !== "=") {
+            if (node.operator !== "=" && !astUtils.isLogicalAssignmentOperator(node.operator)) {
                 context.report({
                     node,
                     messageId: "unexpected",
index 3395feae655f64d4348ffcab8fe227e3c22c82c3..18da5c55b4a4fe346dfdb2250854f2691d75bb2f 100644 (file)
@@ -35,11 +35,8 @@ module.exports = {
                 properties: {
                     overrides: {
                         type: "object",
-                        properties: {
-                            anyOf: {
-                                type: "string",
-                                enum: ["after", "before", "none", "ignore"]
-                            }
+                        additionalProperties: {
+                            enum: ["after", "before", "none", "ignore"]
                         }
                     }
                 },
index fa65eae4e0222d5001831d9cebcca0cdbccb031d..f8b5bd977778f344c24a93df7e9f28c4544574a4 100644 (file)
@@ -58,7 +58,8 @@ module.exports = {
                     allowSingleLineBlocks: {
                         type: "boolean"
                     }
-                }
+                },
+                additionalProperties: false
             }
         ],
 
index eea19f5ce5811b534e2fd6cf2dd933e41382d88c..c97b9956b7136e9c5997de8526157fb70333878b 100644 (file)
@@ -85,10 +85,10 @@ function newNodeTypeTester(type) {
  */
 function isIIFEStatement(node) {
     if (node.type === "ExpressionStatement") {
-        let call = node.expression;
+        let call = astUtils.skipChainExpression(node.expression);
 
         if (call.type === "UnaryExpression") {
-            call = call.argument;
+            call = astUtils.skipChainExpression(call.argument);
         }
         return call.type === "CallExpression" && astUtils.isFunction(call.callee);
     }
index d4e0251940c50348cad10d581377d1f3589f33b4..ee5cfe3c8c7fd9b68d0b1e84a89c61dd1ffc7186 100644 (file)
@@ -5,6 +5,8 @@
 
 "use strict";
 
+const astUtils = require("./utils/ast-utils");
+
 //------------------------------------------------------------------------------
 // Helpers
 //------------------------------------------------------------------------------
@@ -66,6 +68,7 @@ function getCallbackInfo(node) {
     const retv = { isCallback: false, isLexicalThis: false };
     let currentNode = node;
     let parent = node.parent;
+    let bound = false;
 
     while (currentNode) {
         switch (parent.type) {
@@ -73,23 +76,34 @@ function getCallbackInfo(node) {
             // Checks parents recursively.
 
             case "LogicalExpression":
+            case "ChainExpression":
             case "ConditionalExpression":
                 break;
 
             // Checks whether the parent node is `.bind(this)` call.
             case "MemberExpression":
-                if (parent.object === currentNode &&
+                if (
+                    parent.object === currentNode &&
                     !parent.property.computed &&
                     parent.property.type === "Identifier" &&
-                    parent.property.name === "bind" &&
-                    parent.parent.type === "CallExpression" &&
-                    parent.parent.callee === parent
+                    parent.property.name === "bind"
                 ) {
-                    retv.isLexicalThis = (
-                        parent.parent.arguments.length === 1 &&
-                        parent.parent.arguments[0].type === "ThisExpression"
-                    );
-                    parent = parent.parent;
+                    const maybeCallee = parent.parent.type === "ChainExpression"
+                        ? parent.parent
+                        : parent;
+
+                    if (astUtils.isCallee(maybeCallee)) {
+                        if (!bound) {
+                            bound = true; // Use only the first `.bind()` to make `isLexicalThis` value.
+                            retv.isLexicalThis = (
+                                maybeCallee.parent.arguments.length === 1 &&
+                                maybeCallee.parent.arguments[0].type === "ThisExpression"
+                            );
+                        }
+                        parent = maybeCallee.parent;
+                    } else {
+                        return retv;
+                    }
                 } else {
                     return retv;
                 }
@@ -272,7 +286,7 @@ module.exports = {
                     context.report({
                         node,
                         messageId: "preferArrowCallback",
-                        fix(fixer) {
+                        *fix(fixer) {
                             if ((!callbackInfo.isLexicalThis && scopeInfo.this) || hasDuplicateParams(node.params)) {
 
                                 /*
@@ -281,30 +295,81 @@ module.exports = {
                                  * If the callback function has duplicates in its list of parameters (possible in sloppy mode),
                                  * don't replace it with an arrow function, because this is a SyntaxError with arrow functions.
                                  */
-                                return null;
+                                return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
                             }
 
-                            const paramsLeftParen = node.params.length ? sourceCode.getTokenBefore(node.params[0]) : sourceCode.getTokenBefore(node.body, 1);
-                            const paramsRightParen = sourceCode.getTokenBefore(node.body);
-                            const asyncKeyword = node.async ? "async " : "";
-                            const paramsFullText = sourceCode.text.slice(paramsLeftParen.range[0], paramsRightParen.range[1]);
-                            const arrowFunctionText = `${asyncKeyword}${paramsFullText} => ${sourceCode.getText(node.body)}`;
+                            // Remove `.bind(this)` if exists.
+                            if (callbackInfo.isLexicalThis) {
+                                const memberNode = node.parent;
 
-                            /*
-                             * If the callback function has `.bind(this)`, replace it with an arrow function and remove the binding.
-                             * Otherwise, just replace the arrow function itself.
-                             */
-                            const replacedNode = callbackInfo.isLexicalThis ? node.parent.parent : node;
+                                /*
+                                 * If `.bind(this)` exists but the parent is not `.bind(this)`, don't remove it automatically.
+                                 * E.g. `(foo || function(){}).bind(this)`
+                                 */
+                                if (memberNode.type !== "MemberExpression") {
+                                    return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
+                                }
+
+                                const callNode = memberNode.parent;
+                                const firstTokenToRemove = sourceCode.getTokenAfter(memberNode.object, astUtils.isNotClosingParenToken);
+                                const lastTokenToRemove = sourceCode.getLastToken(callNode);
+
+                                /*
+                                 * If the member expression is parenthesized, don't remove the right paren.
+                                 * E.g. `(function(){}.bind)(this)`
+                                 *                    ^^^^^^^^^^^^
+                                 */
+                                if (astUtils.isParenthesised(sourceCode, memberNode)) {
+                                    return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
+                                }
+
+                                // If comments exist in the `.bind(this)`, don't remove those.
+                                if (sourceCode.commentsExistBetween(firstTokenToRemove, lastTokenToRemove)) {
+                                    return; // eslint-disable-line eslint-plugin/fixer-return -- false positive
+                                }
+
+                                yield fixer.removeRange([firstTokenToRemove.range[0], lastTokenToRemove.range[1]]);
+                            }
+
+                            // Convert the function expression to an arrow function.
+                            const functionToken = sourceCode.getFirstToken(node, node.async ? 1 : 0);
+                            const leftParenToken = sourceCode.getTokenAfter(functionToken, astUtils.isOpeningParenToken);
+
+                            if (sourceCode.commentsExistBetween(functionToken, leftParenToken)) {
+
+                                // Remove only extra tokens to keep comments.
+                                yield fixer.remove(functionToken);
+                                if (node.id) {
+                                    yield fixer.remove(node.id);
+                                }
+                            } else {
+
+                                // Remove extra tokens and spaces.
+                                yield fixer.removeRange([functionToken.range[0], leftParenToken.range[0]]);
+                            }
+                            yield fixer.insertTextBefore(node.body, "=> ");
+
+                            // Get the node that will become the new arrow function.
+                            let replacedNode = callbackInfo.isLexicalThis ? node.parent.parent : node;
+
+                            if (replacedNode.type === "ChainExpression") {
+                                replacedNode = replacedNode.parent;
+                            }
 
                             /*
                              * If the replaced node is part of a BinaryExpression, LogicalExpression, or MemberExpression, then
                              * the arrow function needs to be parenthesized, because `foo || () => {}` is invalid syntax even
                              * though `foo || function() {}` is valid.
                              */
-                            const needsParens = replacedNode.parent.type !== "CallExpression" && replacedNode.parent.type !== "ConditionalExpression";
-                            const replacementText = needsParens ? `(${arrowFunctionText})` : arrowFunctionText;
-
-                            return fixer.replaceText(replacedNode, replacementText);
+                            if (
+                                replacedNode.parent.type !== "CallExpression" &&
+                                replacedNode.parent.type !== "ConditionalExpression" &&
+                                !astUtils.isParenthesised(sourceCode, replacedNode) &&
+                                !astUtils.isParenthesised(sourceCode, node)
+                            ) {
+                                yield fixer.insertTextBefore(replacedNode, "(");
+                                yield fixer.insertTextAfter(replacedNode, ")");
+                            }
                         }
                     });
                 }
index 1a51956dde53ff916175fa4cb57e4fbebf3c8441..b2d3c8a0b0193e7c749504d1823b983d8e62c709 100644 (file)
@@ -4,6 +4,18 @@
  */
 "use strict";
 
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const PRECEDENCE_OF_ASSIGNMENT_EXPR = astUtils.getPrecedence({ type: "AssignmentExpression" });
+
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
@@ -163,6 +175,8 @@ module.exports = {
             return node.type === "VariableDeclarator" &&
                 node.id.type === "Identifier" &&
                 node.init.type === "MemberExpression" &&
+                !node.init.computed &&
+                node.init.property.type === "Identifier" &&
                 node.id.name === node.init.property.name;
         }
 
@@ -178,9 +192,20 @@ module.exports = {
             const rightNode = node.init;
             const sourceCode = context.getSourceCode();
 
+            // Don't fix if that would remove any comments. Only comments inside `rightNode.object` can be preserved.
+            if (sourceCode.getCommentsInside(node).length > sourceCode.getCommentsInside(rightNode.object).length) {
+                return null;
+            }
+
+            let objectText = sourceCode.getText(rightNode.object);
+
+            if (astUtils.getPrecedence(rightNode.object) < PRECEDENCE_OF_ASSIGNMENT_EXPR) {
+                objectText = `(${objectText})`;
+            }
+
             return fixer.replaceText(
                 node,
-                `{${rightNode.property.name}} = ${sourceCode.getText(rightNode.object)}`
+                `{${rightNode.property.name}} = ${objectText}`
             );
         }
 
index 5e75ef4724f3b7988a64c64fea314977ffe54128..d1a00d6209ef025872f4096bcb3238b086f3969e 100644 (file)
@@ -52,7 +52,7 @@ function doesExponentNeedParens(exponent) {
  * @returns {boolean} `true` if the expression needs to be parenthesised.
  */
 function doesExponentiationExpressionNeedParens(node, sourceCode) {
-    const parent = node.parent;
+    const parent = node.parent.type === "ChainExpression" ? node.parent.parent : node.parent;
 
     const needsParens = (
         parent.type === "ClassDeclaration" ||
index 2a4fb5d954aad533f03c56e29c248306ddfe07a9..cc82e6653c03b3b0d2594d984f2b5128db8853d6 100644 (file)
@@ -29,19 +29,10 @@ const radixMap = new Map([
  * false otherwise.
  */
 function isParseInt(calleeNode) {
-    switch (calleeNode.type) {
-        case "Identifier":
-            return calleeNode.name === "parseInt";
-        case "MemberExpression":
-            return calleeNode.object.type === "Identifier" &&
-                calleeNode.object.name === "Number" &&
-                calleeNode.property.type === "Identifier" &&
-                calleeNode.property.name === "parseInt";
-
-        // no default
-    }
-
-    return false;
+    return (
+        astUtils.isSpecificId(calleeNode, "parseInt") ||
+        astUtils.isSpecificMemberAccess(calleeNode, "Number", "parseInt")
+    );
 }
 
 //------------------------------------------------------------------------------
@@ -112,6 +103,16 @@ module.exports = {
                                 /*
                                  * If the newly-produced literal would be invalid, (e.g. 0b1234),
                                  * or it would yield an incorrect parseInt result for some other reason, don't make a fix.
+                                 *
+                                 * If `str` had numeric separators, `+replacement` will evaluate to `NaN` because unary `+`
+                                 * per the specification doesn't support numeric separators. Thus, the above condition will be `true`
+                                 * (`NaN !== anything` is always `true`) regardless of the `parseInt(str, radix)` value.
+                                 * Consequently, no autofixes will be made. This is correct behavior because `parseInt` also
+                                 * doesn't support numeric separators, but it does parse part of the string before the first `_`,
+                                 * so the autofix would be invalid:
+                                 *
+                                 *   parseInt("1_1", 2) // === 1
+                                 *   0b1_1 // === 3
                                  */
                                 return null;
                             }
index 56911b67adcc998461b5f4fff51b84382a566a95..ec16e445555dbfb3861d3315cdbf6ef31901d92d 100644 (file)
@@ -73,9 +73,7 @@ module.exports = {
          * @returns {boolean} `true` if the call is a Promise.reject() call
          */
         function isPromiseRejectCall(node) {
-            return node.callee.type === "MemberExpression" &&
-                node.callee.object.type === "Identifier" && node.callee.object.name === "Promise" &&
-                node.callee.property.type === "Identifier" && node.callee.property.name === "reject";
+            return astUtils.isSpecificMemberAccess(node.callee, "Promise", "reject");
         }
 
         //----------------------------------------------------------------------
index 47b2b090f829f359380dd8a9885edf66b5d9af92..9e8ce02354701fc15f3fd6f45d23757a1aec6b21 100644 (file)
@@ -25,6 +25,15 @@ function isStringLiteral(node) {
     return node.type === "Literal" && typeof node.value === "string";
 }
 
+/**
+ * Determines whether the given node is a regex literal.
+ * @param {ASTNode} node Node to check.
+ * @returns {boolean} True if the node is a regex literal.
+ */
+function isRegexLiteral(node) {
+    return node.type === "Literal" && Object.prototype.hasOwnProperty.call(node, "regex");
+}
+
 /**
  * Determines whether the given node is a template literal without expressions.
  * @param {ASTNode} node Node to check.
@@ -50,14 +59,28 @@ module.exports = {
             url: "https://eslint.org/docs/rules/prefer-regex-literals"
         },
 
-        schema: [],
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    disallowRedundantWrapping: {
+                        type: "boolean",
+                        default: false
+                    }
+                },
+                additionalProperties: false
+            }
+        ],
 
         messages: {
-            unexpectedRegExp: "Use a regular expression literal instead of the 'RegExp' constructor."
+            unexpectedRegExp: "Use a regular expression literal instead of the 'RegExp' constructor.",
+            unexpectedRedundantRegExp: "Regular expression literal is unnecessarily wrapped within a 'RegExp' constructor.",
+            unexpectedRedundantRegExpWithFlags: "Use regular expression literal with flags instead of the 'RegExp' constructor."
         }
     },
 
     create(context) {
+        const [{ disallowRedundantWrapping = false } = {}] = context.options;
 
         /**
          * Determines whether the given identifier node is a reference to a global variable.
@@ -79,11 +102,8 @@ module.exports = {
          */
         function isStringRawTaggedStaticTemplateLiteral(node) {
             return node.type === "TaggedTemplateExpression" &&
-                node.tag.type === "MemberExpression" &&
-                node.tag.object.type === "Identifier" &&
-                node.tag.object.name === "String" &&
-                isGlobalReference(node.tag.object) &&
-                astUtils.getStaticPropertyName(node.tag) === "raw" &&
+                astUtils.isSpecificMemberAccess(node.tag, "String", "raw") &&
+                isGlobalReference(astUtils.skipChainExpression(node.tag).object) &&
                 isStaticTemplateLiteral(node.quasi);
         }
 
@@ -98,6 +118,40 @@ module.exports = {
                 isStringRawTaggedStaticTemplateLiteral(node);
         }
 
+        /**
+         * Determines whether the relevant arguments of the given are all static string literals.
+         * @param {ASTNode} node Node to check.
+         * @returns {boolean} True if all arguments are static strings.
+         */
+        function hasOnlyStaticStringArguments(node) {
+            const args = node.arguments;
+
+            if ((args.length === 1 || args.length === 2) && args.every(isStaticString)) {
+                return true;
+            }
+
+            return false;
+        }
+
+        /**
+         * Determines whether the arguments of the given node indicate that a regex literal is unnecessarily wrapped.
+         * @param {ASTNode} node Node to check.
+         * @returns {boolean} True if the node already contains a regex literal argument.
+         */
+        function isUnnecessarilyWrappedRegexLiteral(node) {
+            const args = node.arguments;
+
+            if (args.length === 1 && isRegexLiteral(args[0])) {
+                return true;
+            }
+
+            if (args.length === 2 && isRegexLiteral(args[0]) && isStaticString(args[1])) {
+                return true;
+            }
+
+            return false;
+        }
+
         return {
             Program() {
                 const scope = context.getScope();
@@ -110,12 +164,13 @@ module.exports = {
                 };
 
                 for (const { node } of tracker.iterateGlobalReferences(traceMap)) {
-                    const args = node.arguments;
-
-                    if (
-                        (args.length === 1 || args.length === 2) &&
-                        args.every(isStaticString)
-                    ) {
+                    if (disallowRedundantWrapping && isUnnecessarilyWrappedRegexLiteral(node)) {
+                        if (node.arguments.length === 2) {
+                            context.report({ node, messageId: "unexpectedRedundantRegExpWithFlags" });
+                        } else {
+                            context.report({ node, messageId: "unexpectedRedundantRegExp" });
+                        }
+                    } else if (hasOnlyStaticStringArguments(node)) {
                         context.report({ node, messageId: "unexpectedRegExp" });
                     }
                 }
index bcb0dc0dd4c1a968a224d1c63782cd9a87e66f62..d3c3c4d22972a12e85c0466162541c4392c2894b 100644 (file)
@@ -18,17 +18,13 @@ const astUtils = require("./utils/ast-utils");
  */
 function isVariadicApplyCalling(node) {
     return (
-        node.callee.type === "MemberExpression" &&
-        node.callee.property.type === "Identifier" &&
-        node.callee.property.name === "apply" &&
-        node.callee.computed === false &&
+        astUtils.isSpecificMemberAccess(node.callee, null, "apply") &&
         node.arguments.length === 2 &&
         node.arguments[1].type !== "ArrayExpression" &&
         node.arguments[1].type !== "SpreadElement"
     );
 }
 
-
 /**
  * Checks whether or not `thisArg` is not changed by `.apply()`.
  * @param {ASTNode|null} expectedThis The node that is the owner of the applied function.
@@ -75,7 +71,7 @@ module.exports = {
                     return;
                 }
 
-                const applied = node.callee.object;
+                const applied = astUtils.skipChainExpression(astUtils.skipChainExpression(node.callee).object);
                 const expectedThis = (applied.type === "MemberExpression") ? applied.object : null;
                 const thisArg = node.arguments[0];
 
index e8f980ebd38c6ff95289fa03aac5605a19f6fec0..cb967660a62b8659873daac5c2a6b94ef0baffb9 100644 (file)
@@ -39,33 +39,25 @@ function getTopConcatBinaryExpression(node) {
 }
 
 /**
- * Determines whether a given node is a octal escape sequence
+ * Checks whether or not a node contains a string literal with an octal or non-octal decimal escape sequence
  * @param {ASTNode} node A node to check
- * @returns {boolean} `true` if the node is an octal escape sequence
+ * @returns {boolean} `true` if at least one string literal within the node contains
+ * an octal or non-octal decimal escape sequence
  */
-function isOctalEscapeSequence(node) {
-
-    // No need to check TemplateLiterals – would throw error with octal escape
-    const isStringLiteral = node.type === "Literal" && typeof node.value === "string";
-
-    if (!isStringLiteral) {
-        return false;
+function hasOctalOrNonOctalDecimalEscapeSequence(node) {
+    if (isConcatenation(node)) {
+        return (
+            hasOctalOrNonOctalDecimalEscapeSequence(node.left) ||
+            hasOctalOrNonOctalDecimalEscapeSequence(node.right)
+        );
     }
 
-    return astUtils.hasOctalEscapeSequence(node.raw);
-}
-
-/**
- * Checks whether or not a node contains a octal escape sequence
- * @param {ASTNode} node A node to check
- * @returns {boolean} `true` if the node contains an octal escape sequence
- */
-function hasOctalEscapeSequence(node) {
-    if (isConcatenation(node)) {
-        return hasOctalEscapeSequence(node.left) || hasOctalEscapeSequence(node.right);
+    // No need to check TemplateLiterals – would throw parsing error
+    if (node.type === "Literal" && typeof node.value === "string") {
+        return astUtils.hasOctalOrNonOctalDecimalEscapeSequence(node.raw);
     }
 
-    return isOctalEscapeSequence(node);
+    return false;
 }
 
 /**
@@ -237,7 +229,7 @@ module.exports = {
         function fixNonStringBinaryExpression(fixer, node) {
             const topBinaryExpr = getTopConcatBinaryExpression(node.parent);
 
-            if (hasOctalEscapeSequence(topBinaryExpr)) {
+            if (hasOctalOrNonOctalDecimalEscapeSequence(topBinaryExpr)) {
                 return null;
             }
 
index d1f4443b9033c99b9eada4e72c87a9bf1be77c0e..da7e127493eaa1f89be7490a4d602572a0f3838f 100644 (file)
@@ -282,9 +282,12 @@ module.exports = {
                                 description: settings.description
                             },
                             fix(fixer) {
-                                if (quoteOption === "backtick" && astUtils.hasOctalEscapeSequence(rawVal)) {
+                                if (quoteOption === "backtick" && astUtils.hasOctalOrNonOctalDecimalEscapeSequence(rawVal)) {
 
-                                    // An octal escape sequence in a template literal would produce syntax error, even in non-strict mode.
+                                    /*
+                                     * An octal or non-octal decimal escape sequence in a template literal would
+                                     * produce syntax error, even in non-strict mode.
+                                     */
                                     return null;
                                 }
 
index 3903cb2a6a2b01b75d2c70ce2ea9873fd4eddcfc..e322566238809b3b403475d1669b76b8bf598da3 100644 (file)
@@ -166,9 +166,12 @@ module.exports = {
                 if (variable && !isShadowed(variable)) {
                     variable.references.forEach(reference => {
                         const node = reference.identifier.parent;
+                        const maybeCallee = node.parent.type === "ChainExpression"
+                            ? node.parent
+                            : node;
 
-                        if (isParseIntMethod(node) && astUtils.isCallee(node)) {
-                            checkArguments(node.parent);
+                        if (isParseIntMethod(node) && astUtils.isCallee(maybeCallee)) {
+                            checkArguments(maybeCallee.parent);
                         }
                     });
                 }
index 936e7661ef4fb3ce1827cc793d976c62e0463527..5c546f2902870ef6dddf5d7d9651ff6d717203ef 100644 (file)
@@ -223,6 +223,7 @@ module.exports = {
             BreakStatement: checkNode,
             ContinueStatement: checkNode,
             DebuggerStatement: checkNode,
+            DoWhileStatement: checkNode,
             ReturnStatement: checkNode,
             ThrowStatement: checkNode,
             ImportDeclaration: checkNode,
index 65ad9a18a93112a5ec75770bf9ac84c5a2f82349..4c3ddec766960a1a395d00b5ff6bc5d605953429 100644 (file)
@@ -44,6 +44,10 @@ module.exports = {
                     ignoreMemberSort: {
                         type: "boolean",
                         default: false
+                    },
+                    allowSeparatedGroups: {
+                        type: "boolean",
+                        default: false
                     }
                 },
                 additionalProperties: false
@@ -66,6 +70,7 @@ module.exports = {
             ignoreDeclarationSort = configuration.ignoreDeclarationSort || false,
             ignoreMemberSort = configuration.ignoreMemberSort || false,
             memberSyntaxSortOrder = configuration.memberSyntaxSortOrder || ["none", "all", "multiple", "single"],
+            allowSeparatedGroups = configuration.allowSeparatedGroups || false,
             sourceCode = context.getSourceCode();
         let previousDeclaration = null;
 
@@ -115,9 +120,32 @@ module.exports = {
 
         }
 
+        /**
+         * Calculates number of lines between two nodes. It is assumed that the given `left` node appears before
+         * the given `right` node in the source code. Lines are counted from the end of the `left` node till the
+         * start of the `right` node. If the given nodes are on the same line, it returns `0`, same as if they were
+         * on two consecutive lines.
+         * @param {ASTNode} left node that appears before the given `right` node.
+         * @param {ASTNode} right node that appears after the given `left` node.
+         * @returns {number} number of lines between nodes.
+         */
+        function getNumberOfLinesBetween(left, right) {
+            return Math.max(right.loc.start.line - left.loc.end.line - 1, 0);
+        }
+
         return {
             ImportDeclaration(node) {
                 if (!ignoreDeclarationSort) {
+                    if (
+                        previousDeclaration &&
+                        allowSeparatedGroups &&
+                        getNumberOfLinesBetween(previousDeclaration, node) > 0
+                    ) {
+
+                        // reset declaration sort
+                        previousDeclaration = null;
+                    }
+
                     if (previousDeclaration) {
                         const currentMemberSyntaxGroupIndex = getMemberParameterGroupIndex(node),
                             previousMemberSyntaxGroupIndex = getMemberParameterGroupIndex(previousDeclaration);
index 9b56481bf355fbbb45a31a846f4401249d11fed6..87ef9bfd88013cbcc85f21ea598b173bd108a2c4 100644 (file)
@@ -5,8 +5,31 @@
 
 "use strict";
 
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
 const astUtils = require("./utils/ast-utils");
 
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks whether the given node represents the body of a function.
+ * @param {ASTNode} node the node to check.
+ * @returns {boolean} `true` if the node is function body.
+ */
+function isFunctionBody(node) {
+    const parent = node.parent;
+
+    return (
+        node.type === "BlockStatement" &&
+        astUtils.isFunction(parent) &&
+        parent.body === node
+    );
+}
+
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
@@ -82,13 +105,16 @@ module.exports = {
         }
 
         /**
-         * Checks whether or not a given token is an arrow operator (=>) or a keyword
-         * in order to avoid to conflict with `arrow-spacing` and `keyword-spacing`.
-         * @param {Token} token A token to check.
-         * @returns {boolean} `true` if the token is an arrow operator.
+         * Checks whether the spacing before the given block is already controlled by another rule:
+         * - `arrow-spacing` checks spaces after `=>`.
+         * - `keyword-spacing` checks spaces after keywords in certain contexts.
+         * @param {Token} precedingToken first token before the block.
+         * @param {ASTNode|Token} node `BlockStatement` node or `{` token of a `SwitchStatement` node.
+         * @returns {boolean} `true` if requiring or disallowing spaces before the given block could produce conflicts with other rules.
          */
-        function isConflicted(token) {
-            return (token.type === "Punctuator" && token.value === "=>") || token.type === "Keyword";
+        function isConflicted(precedingToken, node) {
+            return astUtils.isArrowToken(precedingToken) ||
+                astUtils.isKeywordToken(precedingToken) && !isFunctionBody(node);
         }
 
         /**
@@ -99,13 +125,12 @@ module.exports = {
         function checkPrecedingSpace(node) {
             const precedingToken = sourceCode.getTokenBefore(node);
 
-            if (precedingToken && !isConflicted(precedingToken) && astUtils.isTokenOnSameLine(precedingToken, node)) {
+            if (precedingToken && !isConflicted(precedingToken, node) && astUtils.isTokenOnSameLine(precedingToken, node)) {
                 const hasSpace = sourceCode.isSpaceBetweenTokens(precedingToken, node);
-                const parent = context.getAncestors().pop();
                 let requireSpace;
                 let requireNoSpace;
 
-                if (parent.type === "FunctionExpression" || parent.type === "FunctionDeclaration") {
+                if (isFunctionBody(node)) {
                     requireSpace = alwaysFunctions;
                     requireNoSpace = neverFunctions;
                 } else if (node.type === "ClassBody") {
index 7b466be75f2e5ee220672545aa8bfa5dae0a2c15..53ffeb7e6d153e7b57e5e2685baacbcdc7856bd7 100644 (file)
@@ -106,7 +106,7 @@ module.exports = {
          * @returns {void}
          */
         function checkCallExpression(node) {
-            const callee = node.callee;
+            const callee = astUtils.skipChainExpression(node.callee);
 
             if (callee.type === "MemberExpression") {
                 const methodName = astUtils.getStaticPropertyName(callee);
index ecea6948da236040a023745bd16eee77f9153f76..1fd6340df7c7e46d06c15ea2e8b3215aae5ff86c 100644 (file)
@@ -37,8 +37,12 @@ const LINEBREAKS = new Set(["\r\n", "\r", "\n", "\u2028", "\u2029"]);
 // A set of node types that can contain a list of statements
 const STATEMENT_LIST_PARENTS = new Set(["Program", "BlockStatement", "SwitchCase"]);
 
-const DECIMAL_INTEGER_PATTERN = /^(0|[1-9]\d*)$/u;
-const OCTAL_ESCAPE_PATTERN = /^(?:[^\\]|\\[^0-7]|\\0(?![0-9]))*\\(?:[1-7]|0[0-9])/u;
+const DECIMAL_INTEGER_PATTERN = /^(?:0|0[0-7]*[89]\d*|[1-9](?:_?\d)*)$/u;
+
+// Tests the presence of at least one LegacyOctalEscapeSequence or NonOctalDecimalEscapeSequence in a raw string
+const OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN = /^(?:[^\\]|\\.)*\\(?:[1-9]|0[0-9])/su;
+
+const LOGICAL_ASSIGNMENT_OPERATORS = new Set(["&&=", "||=", "??="]);
 
 /**
  * Checks reference if is non initializer and writable.
@@ -143,6 +147,23 @@ function isInLoop(node) {
     return false;
 }
 
+/**
+ * Determines whether the given node is a `null` literal.
+ * @param {ASTNode} node The node to check
+ * @returns {boolean} `true` if the node is a `null` literal
+ */
+function isNullLiteral(node) {
+
+    /*
+     * Checking `node.value === null` does not guarantee that a literal is a null literal.
+     * When parsing values that cannot be represented in the current environment (e.g. unicode
+     * regexes in Node 4), `node.value` is set to `null` because it wouldn't be possible to
+     * set `node.value` to a unicode regex. To make sure a literal is actually `null`, check
+     * `node.regex` instead. Also see: https://github.com/eslint/eslint/issues/8020
+     */
+    return node.type === "Literal" && node.value === null && !node.regex && !node.bigint;
+}
+
 /**
  * Checks whether or not a node is `null` or `undefined`.
  * @param {ASTNode} node A node to check.
@@ -151,7 +172,7 @@ function isInLoop(node) {
  */
 function isNullOrUndefined(node) {
     return (
-        module.exports.isNullLiteral(node) ||
+        isNullLiteral(node) ||
         (node.type === "Identifier" && node.name === "undefined") ||
         (node.type === "UnaryExpression" && node.operator === "void")
     );
@@ -166,20 +187,270 @@ function isCallee(node) {
     return node.parent.type === "CallExpression" && node.parent.callee === node;
 }
 
+/**
+ * Returns the result of the string conversion applied to the evaluated value of the given expression node,
+ * if it can be determined statically.
+ *
+ * This function returns a `string` value for all `Literal` nodes and simple `TemplateLiteral` nodes only.
+ * In all other cases, this function returns `null`.
+ * @param {ASTNode} node Expression node.
+ * @returns {string|null} String value if it can be determined. Otherwise, `null`.
+ */
+function getStaticStringValue(node) {
+    switch (node.type) {
+        case "Literal":
+            if (node.value === null) {
+                if (isNullLiteral(node)) {
+                    return String(node.value); // "null"
+                }
+                if (node.regex) {
+                    return `/${node.regex.pattern}/${node.regex.flags}`;
+                }
+                if (node.bigint) {
+                    return node.bigint;
+                }
+
+                // Otherwise, this is an unknown literal. The function will return null.
+
+            } else {
+                return String(node.value);
+            }
+            break;
+        case "TemplateLiteral":
+            if (node.expressions.length === 0 && node.quasis.length === 1) {
+                return node.quasis[0].value.cooked;
+            }
+            break;
+
+            // no default
+    }
+
+    return null;
+}
+
+/**
+ * Gets the property name of a given node.
+ * The node can be a MemberExpression, a Property, or a MethodDefinition.
+ *
+ * If the name is dynamic, this returns `null`.
+ *
+ * For examples:
+ *
+ *     a.b           // => "b"
+ *     a["b"]        // => "b"
+ *     a['b']        // => "b"
+ *     a[`b`]        // => "b"
+ *     a[100]        // => "100"
+ *     a[b]          // => null
+ *     a["a" + "b"]  // => null
+ *     a[tag`b`]     // => null
+ *     a[`${b}`]     // => null
+ *
+ *     let a = {b: 1}            // => "b"
+ *     let a = {["b"]: 1}        // => "b"
+ *     let a = {['b']: 1}        // => "b"
+ *     let a = {[`b`]: 1}        // => "b"
+ *     let a = {[100]: 1}        // => "100"
+ *     let a = {[b]: 1}          // => null
+ *     let a = {["a" + "b"]: 1}  // => null
+ *     let a = {[tag`b`]: 1}     // => null
+ *     let a = {[`${b}`]: 1}     // => null
+ * @param {ASTNode} node The node to get.
+ * @returns {string|null} The property name if static. Otherwise, null.
+ */
+function getStaticPropertyName(node) {
+    let prop;
+
+    switch (node && node.type) {
+        case "ChainExpression":
+            return getStaticPropertyName(node.expression);
+
+        case "Property":
+        case "MethodDefinition":
+            prop = node.key;
+            break;
+
+        case "MemberExpression":
+            prop = node.property;
+            break;
+
+            // no default
+    }
+
+    if (prop) {
+        if (prop.type === "Identifier" && !node.computed) {
+            return prop.name;
+        }
+
+        return getStaticStringValue(prop);
+    }
+
+    return null;
+}
+
+/**
+ * Retrieve `ChainExpression#expression` value if the given node a `ChainExpression` node. Otherwise, pass through it.
+ * @param {ASTNode} node The node to address.
+ * @returns {ASTNode} The `ChainExpression#expression` value if the node is a `ChainExpression` node. Otherwise, the node.
+ */
+function skipChainExpression(node) {
+    return node && node.type === "ChainExpression" ? node.expression : node;
+}
+
+/**
+ * Check if the `actual` is an expected value.
+ * @param {string} actual The string value to check.
+ * @param {string | RegExp} expected The expected string value or pattern.
+ * @returns {boolean} `true` if the `actual` is an expected value.
+ */
+function checkText(actual, expected) {
+    return typeof expected === "string"
+        ? actual === expected
+        : expected.test(actual);
+}
+
+/**
+ * Check if a given node is an Identifier node with a given name.
+ * @param {ASTNode} node The node to check.
+ * @param {string | RegExp} name The expected name or the expected pattern of the object name.
+ * @returns {boolean} `true` if the node is an Identifier node with the name.
+ */
+function isSpecificId(node, name) {
+    return node.type === "Identifier" && checkText(node.name, name);
+}
+
+/**
+ * Check if a given node is member access with a given object name and property name pair.
+ * This is regardless of optional or not.
+ * @param {ASTNode} node The node to check.
+ * @param {string | RegExp | null} objectName The expected name or the expected pattern of the object name. If this is nullish, this method doesn't check object.
+ * @param {string | RegExp | null} propertyName The expected name or the expected pattern of the property name. If this is nullish, this method doesn't check property.
+ * @returns {boolean} `true` if the node is member access with the object name and property name pair.
+ * The node is a `MemberExpression` or `ChainExpression`.
+ */
+function isSpecificMemberAccess(node, objectName, propertyName) {
+    const checkNode = skipChainExpression(node);
+
+    if (checkNode.type !== "MemberExpression") {
+        return false;
+    }
+
+    if (objectName && !isSpecificId(checkNode.object, objectName)) {
+        return false;
+    }
+
+    if (propertyName) {
+        const actualPropertyName = getStaticPropertyName(checkNode);
+
+        if (typeof actualPropertyName !== "string" || !checkText(actualPropertyName, propertyName)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+/**
+ * Check if two literal nodes are the same value.
+ * @param {ASTNode} left The Literal node to compare.
+ * @param {ASTNode} right The other Literal node to compare.
+ * @returns {boolean} `true` if the two literal nodes are the same value.
+ */
+function equalLiteralValue(left, right) {
+
+    // RegExp literal.
+    if (left.regex || right.regex) {
+        return Boolean(
+            left.regex &&
+            right.regex &&
+            left.regex.pattern === right.regex.pattern &&
+            left.regex.flags === right.regex.flags
+        );
+    }
+
+    // BigInt literal.
+    if (left.bigint || right.bigint) {
+        return left.bigint === right.bigint;
+    }
+
+    return left.value === right.value;
+}
+
+/**
+ * Check if two expressions reference the same value. For example:
+ *     a = a
+ *     a.b = a.b
+ *     a[0] = a[0]
+ *     a['b'] = a['b']
+ * @param {ASTNode} left The left side of the comparison.
+ * @param {ASTNode} right The right side of the comparison.
+ * @param {boolean} [disableStaticComputedKey] Don't address `a.b` and `a["b"]` are the same if `true`. For backward compatibility.
+ * @returns {boolean} `true` if both sides match and reference the same value.
+ */
+function isSameReference(left, right, disableStaticComputedKey = false) {
+    if (left.type !== right.type) {
+
+        // Handle `a.b` and `a?.b` are samely.
+        if (left.type === "ChainExpression") {
+            return isSameReference(left.expression, right, disableStaticComputedKey);
+        }
+        if (right.type === "ChainExpression") {
+            return isSameReference(left, right.expression, disableStaticComputedKey);
+        }
+
+        return false;
+    }
+
+    switch (left.type) {
+        case "Super":
+        case "ThisExpression":
+            return true;
+
+        case "Identifier":
+            return left.name === right.name;
+        case "Literal":
+            return equalLiteralValue(left, right);
+
+        case "ChainExpression":
+            return isSameReference(left.expression, right.expression, disableStaticComputedKey);
+
+        case "MemberExpression": {
+            if (!disableStaticComputedKey) {
+                const nameA = getStaticPropertyName(left);
+
+                // x.y = x["y"]
+                if (nameA !== null) {
+                    return (
+                        isSameReference(left.object, right.object, disableStaticComputedKey) &&
+                        nameA === getStaticPropertyName(right)
+                    );
+                }
+            }
+
+            /*
+             * x[0] = x[0]
+             * x[y] = x[y]
+             * x.y = x.y
+             */
+            return (
+                left.computed === right.computed &&
+                isSameReference(left.object, right.object, disableStaticComputedKey) &&
+                isSameReference(left.property, right.property, disableStaticComputedKey)
+            );
+        }
+
+        default:
+            return false;
+    }
+}
+
 /**
  * Checks whether or not a node is `Reflect.apply`.
  * @param {ASTNode} node A node to check.
  * @returns {boolean} Whether or not the node is a `Reflect.apply`.
  */
 function isReflectApply(node) {
-    return (
-        node.type === "MemberExpression" &&
-        node.object.type === "Identifier" &&
-        node.object.name === "Reflect" &&
-        node.property.type === "Identifier" &&
-        node.property.name === "apply" &&
-        node.computed === false
-    );
+    return isSpecificMemberAccess(node, "Reflect", "apply");
 }
 
 /**
@@ -188,14 +459,7 @@ function isReflectApply(node) {
  * @returns {boolean} Whether or not the node is a `Array.from`.
  */
 function isArrayFromMethod(node) {
-    return (
-        node.type === "MemberExpression" &&
-        node.object.type === "Identifier" &&
-        arrayOrTypedArrayPattern.test(node.object.name) &&
-        node.property.type === "Identifier" &&
-        node.property.name === "from" &&
-        node.computed === false
-    );
+    return isSpecificMemberAccess(node, arrayOrTypedArrayPattern, "from");
 }
 
 /**
@@ -204,17 +468,7 @@ function isArrayFromMethod(node) {
  * @returns {boolean} Whether or not the node is a method which has `thisArg`.
  */
 function isMethodWhichHasThisArg(node) {
-    for (
-        let currentNode = node;
-        currentNode.type === "MemberExpression" && !currentNode.computed;
-        currentNode = currentNode.property
-    ) {
-        if (currentNode.property.type === "Identifier") {
-            return arrayMethodPattern.test(currentNode.property.name);
-        }
-    }
-
-    return false;
+    return isSpecificMemberAccess(node, null, arrayMethodPattern);
 }
 
 /**
@@ -289,6 +543,15 @@ function isDotToken(token) {
     return token.value === "." && token.type === "Punctuator";
 }
 
+/**
+ * Checks if the given token is a `?.` token or not.
+ * @param {Token} token The token to check.
+ * @returns {boolean} `true` if the token is a `?.` token.
+ */
+function isQuestionDotToken(token) {
+    return token.value === "?." && token.type === "Punctuator";
+}
+
 /**
  * Checks if the given token is a semicolon token or not.
  * @param {Token} token The token to check.
@@ -463,6 +726,15 @@ function isMixedLogicalAndCoalesceExpressions(left, right) {
     );
 }
 
+/**
+ * Checks if the given operator is a logical assignment operator.
+ * @param {string} operator The operator to check.
+ * @returns {boolean} `true` if the operator is a logical assignment operator.
+ */
+function isLogicalAssignmentOperator(operator) {
+    return LOGICAL_ASSIGNMENT_OPERATORS.has(operator);
+}
+
 //------------------------------------------------------------------------------
 // Public Interface
 //------------------------------------------------------------------------------
@@ -505,6 +777,7 @@ module.exports = {
     isCommaToken,
     isCommentToken,
     isDotToken,
+    isQuestionDotToken,
     isKeywordToken,
     isNotClosingBraceToken: negate(isClosingBraceToken),
     isNotClosingBracketToken: negate(isClosingBracketToken),
@@ -512,6 +785,7 @@ module.exports = {
     isNotColonToken: negate(isColonToken),
     isNotCommaToken: negate(isCommaToken),
     isNotDotToken: negate(isDotToken),
+    isNotQuestionDotToken: negate(isQuestionDotToken),
     isNotOpeningBraceToken: negate(isOpeningBraceToken),
     isNotOpeningBracketToken: negate(isOpeningBracketToken),
     isNotOpeningParenToken: negate(isOpeningParenToken),
@@ -669,6 +943,7 @@ module.exports = {
                  */
                 case "LogicalExpression":
                 case "ConditionalExpression":
+                case "ChainExpression":
                     currentNode = parent;
                     break;
 
@@ -755,14 +1030,21 @@ module.exports = {
                  *   (function foo() { ... }).apply(obj, []);
                  */
                 case "MemberExpression":
-                    return (
-                        parent.object !== currentNode ||
-                        parent.property.type !== "Identifier" ||
-                        !bindOrCallOrApplyPattern.test(parent.property.name) ||
-                        !isCallee(parent) ||
-                        parent.parent.arguments.length === 0 ||
-                        isNullOrUndefined(parent.parent.arguments[0])
-                    );
+                    if (
+                        parent.object === currentNode &&
+                        isSpecificMemberAccess(parent, null, bindOrCallOrApplyPattern)
+                    ) {
+                        const maybeCalleeNode = parent.parent.type === "ChainExpression"
+                            ? parent.parent
+                            : parent;
+
+                        return !(
+                            isCallee(maybeCalleeNode) &&
+                            maybeCalleeNode.parent.arguments.length >= 1 &&
+                            !isNullOrUndefined(maybeCalleeNode.parent.arguments[0])
+                        );
+                    }
+                    return true;
 
                 /*
                  * e.g.
@@ -884,6 +1166,7 @@ module.exports = {
                 return 17;
 
             case "CallExpression":
+            case "ChainExpression":
             case "ImportExpression":
                 return 18;
 
@@ -913,104 +1196,6 @@ module.exports = {
         return isFunction(node) && module.exports.isEmptyBlock(node.body);
     },
 
-    /**
-     * Returns the result of the string conversion applied to the evaluated value of the given expression node,
-     * if it can be determined statically.
-     *
-     * This function returns a `string` value for all `Literal` nodes and simple `TemplateLiteral` nodes only.
-     * In all other cases, this function returns `null`.
-     * @param {ASTNode} node Expression node.
-     * @returns {string|null} String value if it can be determined. Otherwise, `null`.
-     */
-    getStaticStringValue(node) {
-        switch (node.type) {
-            case "Literal":
-                if (node.value === null) {
-                    if (module.exports.isNullLiteral(node)) {
-                        return String(node.value); // "null"
-                    }
-                    if (node.regex) {
-                        return `/${node.regex.pattern}/${node.regex.flags}`;
-                    }
-                    if (node.bigint) {
-                        return node.bigint;
-                    }
-
-                    // Otherwise, this is an unknown literal. The function will return null.
-
-                } else {
-                    return String(node.value);
-                }
-                break;
-            case "TemplateLiteral":
-                if (node.expressions.length === 0 && node.quasis.length === 1) {
-                    return node.quasis[0].value.cooked;
-                }
-                break;
-
-            // no default
-        }
-
-        return null;
-    },
-
-    /**
-     * Gets the property name of a given node.
-     * The node can be a MemberExpression, a Property, or a MethodDefinition.
-     *
-     * If the name is dynamic, this returns `null`.
-     *
-     * For examples:
-     *
-     *     a.b           // => "b"
-     *     a["b"]        // => "b"
-     *     a['b']        // => "b"
-     *     a[`b`]        // => "b"
-     *     a[100]        // => "100"
-     *     a[b]          // => null
-     *     a["a" + "b"]  // => null
-     *     a[tag`b`]     // => null
-     *     a[`${b}`]     // => null
-     *
-     *     let a = {b: 1}            // => "b"
-     *     let a = {["b"]: 1}        // => "b"
-     *     let a = {['b']: 1}        // => "b"
-     *     let a = {[`b`]: 1}        // => "b"
-     *     let a = {[100]: 1}        // => "100"
-     *     let a = {[b]: 1}          // => null
-     *     let a = {["a" + "b"]: 1}  // => null
-     *     let a = {[tag`b`]: 1}     // => null
-     *     let a = {[`${b}`]: 1}     // => null
-     * @param {ASTNode} node The node to get.
-     * @returns {string|null} The property name if static. Otherwise, null.
-     */
-    getStaticPropertyName(node) {
-        let prop;
-
-        switch (node && node.type) {
-            case "Property":
-            case "MethodDefinition":
-                prop = node.key;
-                break;
-
-            case "MemberExpression":
-                prop = node.property;
-                break;
-
-            // no default
-        }
-
-        if (prop) {
-            if (prop.type === "Identifier" && !node.computed) {
-                return prop.name;
-            }
-
-            return module.exports.getStaticStringValue(prop);
-        }
-
-        return null;
-    },
-
     /**
      * Get directives from directive prologue of a Program or Function node.
      * @param {ASTNode} node The node to check.
@@ -1056,16 +1241,27 @@ module.exports = {
      * @returns {boolean} `true` if this node is a decimal integer.
      * @example
      *
-     * 5       // true
-     * 5.      // false
-     * 5.0     // false
-     * 05      // false
-     * 0x5     // false
-     * 0b101   // false
-     * 0o5     // false
-     * 5e0     // false
-     * '5'     // false
-     * 5n      // false
+     * 0         // true
+     * 5         // true
+     * 50        // true
+     * 5_000     // true
+     * 1_234_56  // true
+     * 08        // true
+     * 0192      // true
+     * 5.        // false
+     * .5        // false
+     * 5.0       // false
+     * 5.00_00   // false
+     * 05        // false
+     * 0x5       // false
+     * 0b101     // false
+     * 0b11_01   // false
+     * 0o5       // false
+     * 5e0       // false
+     * 5e1_000   // false
+     * 5n        // false
+     * 1_000n    // false
+     * '5'       // false
      */
     isDecimalInteger(node) {
         return node.type === "Literal" && typeof node.value === "number" &&
@@ -1164,7 +1360,7 @@ module.exports = {
         if (node.id) {
             tokens.push(`'${node.id.name}'`);
         } else {
-            const name = module.exports.getStaticPropertyName(parent);
+            const name = getStaticPropertyName(parent);
 
             if (name !== null) {
                 tokens.push(`'${name}'`);
@@ -1391,10 +1587,24 @@ module.exports = {
             case "TaggedTemplateExpression":
             case "YieldExpression":
             case "AwaitExpression":
+            case "ChainExpression":
                 return true; // possibly an error object.
 
             case "AssignmentExpression":
-                return module.exports.couldBeError(node.right);
+                if (["=", "&&="].includes(node.operator)) {
+                    return module.exports.couldBeError(node.right);
+                }
+
+                if (["||=", "??="].includes(node.operator)) {
+                    return module.exports.couldBeError(node.left) || module.exports.couldBeError(node.right);
+                }
+
+                /**
+                 * All other assignment operators are mathematical assignment operators (arithmetic or bitwise).
+                 * An assignment expression with a mathematical operator can either evaluate to a primitive value,
+                 * or throw, depending on the operands. Thus, it cannot evaluate to an `Error` object.
+                 */
+                return false;
 
             case "SequenceExpression": {
                 const exprs = node.expressions;
@@ -1403,6 +1613,17 @@ module.exports = {
             }
 
             case "LogicalExpression":
+
+                /*
+                 * If the && operator short-circuits, the left side was falsy and therefore not an error, and if it
+                 * doesn't short-circuit, it takes the value from the right side, so the right side must always be
+                 * a plausible error. A future improvement could verify that the left side could be truthy by
+                 * excluding falsy literals.
+                 */
+                if (node.operator === "&&") {
+                    return module.exports.couldBeError(node.right);
+                }
+
                 return module.exports.couldBeError(node.left) || module.exports.couldBeError(node.right);
 
             case "ConditionalExpression":
@@ -1413,23 +1634,6 @@ module.exports = {
         }
     },
 
-    /**
-     * Determines whether the given node is a `null` literal.
-     * @param {ASTNode} node The node to check
-     * @returns {boolean} `true` if the node is a `null` literal
-     */
-    isNullLiteral(node) {
-
-        /*
-         * Checking `node.value === null` does not guarantee that a literal is a null literal.
-         * When parsing values that cannot be represented in the current environment (e.g. unicode
-         * regexes in Node 4), `node.value` is set to `null` because it wouldn't be possible to
-         * set `node.value` to a unicode regex. To make sure a literal is actually `null`, check
-         * `node.regex` instead. Also see: https://github.com/eslint/eslint/issues/8020
-         */
-        return node.type === "Literal" && node.value === null && !node.regex && !node.bigint;
-    },
-
     /**
      * Check if a given node is a numeric literal or not.
      * @param {ASTNode} node The node to check.
@@ -1575,20 +1779,31 @@ module.exports = {
     },
 
     /**
-     * Determines whether the given raw string contains an octal escape sequence.
+     * Determines whether the given raw string contains an octal escape sequence
+     * or a non-octal decimal escape sequence ("\8", "\9").
      *
-     * "\1", "\2" ... "\7"
-     * "\00", "\01" ... "\09"
+     * "\1", "\2" ... "\7", "\8", "\9"
+     * "\00", "\01" ... "\07", "\08", "\09"
      *
      * "\0", when not followed by a digit, is not an octal escape sequence.
      * @param {string} rawString A string in its raw representation.
-     * @returns {boolean} `true` if the string contains at least one octal escape sequence.
+     * @returns {boolean} `true` if the string contains at least one octal escape sequence
+     * or at least one non-octal decimal escape sequence.
      */
-    hasOctalEscapeSequence(rawString) {
-        return OCTAL_ESCAPE_PATTERN.test(rawString);
+    hasOctalOrNonOctalDecimalEscapeSequence(rawString) {
+        return OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN.test(rawString);
     },
 
     isLogicalExpression,
     isCoalesceExpression,
-    isMixedLogicalAndCoalesceExpressions
+    isMixedLogicalAndCoalesceExpressions,
+    isNullLiteral,
+    getStaticStringValue,
+    getStaticPropertyName,
+    skipChainExpression,
+    isSpecificId,
+    isSpecificMemberAccess,
+    equalLiteralValue,
+    isSameReference,
+    isLogicalAssignmentOperator
 };
index 896aed63de52220d982c88bd5bc2adf1df00a20b..07e5b84a4a5ae45873152b4b34fe5ccae31dd9d0 100644 (file)
@@ -23,7 +23,14 @@ const eslintUtils = require("eslint-utils");
  * @private
  */
 function isCalleeOfNewExpression(node) {
-    return node.parent.type === "NewExpression" && node.parent.callee === node;
+    const maybeCallee = node.parent.type === "ChainExpression"
+        ? node.parent
+        : node;
+
+    return (
+        maybeCallee.parent.type === "NewExpression" &&
+        maybeCallee.parent.callee === maybeCallee
+    );
 }
 
 //------------------------------------------------------------------------------
@@ -98,7 +105,7 @@ module.exports = {
          * @returns {ASTNode} node that is the function expression of the given IIFE, or null if none exist
          */
         function getFunctionNodeFromIIFE(node) {
-            const callee = node.callee;
+            const callee = astUtils.skipChainExpression(node.callee);
 
             if (callee.type === "FunctionExpression") {
                 return callee;
index f1159e5255df79d00058e3deba4f5fa8048bace6..2fca7571113ecf24fea9385bdda21ef6d83f39f8 100644 (file)
@@ -111,59 +111,6 @@ function getNormalizedLiteral(node) {
     return null;
 }
 
-/**
- * Checks whether two expressions reference the same value. For example:
- *     a = a
- *     a.b = a.b
- *     a[0] = a[0]
- *     a['b'] = a['b']
- * @param   {ASTNode} a Left side of the comparison.
- * @param   {ASTNode} b Right side of the comparison.
- * @returns {boolean}   True if both sides match and reference the same value.
- */
-function same(a, b) {
-    if (a.type !== b.type) {
-        return false;
-    }
-
-    switch (a.type) {
-        case "Identifier":
-            return a.name === b.name;
-
-        case "Literal":
-            return a.value === b.value;
-
-        case "MemberExpression": {
-            const nameA = astUtils.getStaticPropertyName(a);
-
-            // x.y = x["y"]
-            if (nameA !== null) {
-                return (
-                    same(a.object, b.object) &&
-                    nameA === astUtils.getStaticPropertyName(b)
-                );
-            }
-
-            /*
-             * x[0] = x[0]
-             * x[y] = x[y]
-             * x.y = x.y
-             */
-            return (
-                a.computed === b.computed &&
-                same(a.object, b.object) &&
-                same(a.property, b.property)
-            );
-        }
-
-        case "ThisExpression":
-            return true;
-
-        default:
-            return false;
-    }
-}
-
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
@@ -236,7 +183,7 @@ module.exports = {
              * @returns {boolean} Whether node is a "between" range test.
              */
             function isBetweenTest() {
-                if (node.operator === "&&" && same(left.right, right.left)) {
+                if (node.operator === "&&" && astUtils.isSameReference(left.right, right.left)) {
                     const leftLiteral = getNormalizedLiteral(left.left);
                     const rightLiteral = getNormalizedLiteral(right.right);
 
@@ -260,7 +207,7 @@ module.exports = {
              * @returns {boolean} Whether node is an "outside" range test.
              */
             function isOutsideTest() {
-                if (node.operator === "||" && same(left.left, right.right)) {
+                if (node.operator === "||" && astUtils.isSameReference(left.left, right.right)) {
                     const leftLiteral = getNormalizedLiteral(left.right);
                     const rightLiteral = getNormalizedLiteral(right.left);
 
@@ -318,36 +265,37 @@ module.exports = {
          * @returns {string} A string representation of the node with the sides and operator flipped
          */
         function getFlippedString(node) {
-            const tokenBefore = sourceCode.getTokenBefore(node);
             const operatorToken = sourceCode.getFirstTokenBetween(
                 node.left,
                 node.right,
                 token => token.value === node.operator
             );
-            const textBeforeOperator = sourceCode
-                .getText()
-                .slice(
-                    sourceCode.getTokenBefore(operatorToken).range[1],
-                    operatorToken.range[0]
-                );
-            const textAfterOperator = sourceCode
-                .getText()
-                .slice(
-                    operatorToken.range[1],
-                    sourceCode.getTokenAfter(operatorToken).range[0]
-                );
-            const leftText = sourceCode
-                .getText()
-                .slice(
-                    node.range[0],
-                    sourceCode.getTokenBefore(operatorToken).range[1]
-                );
+            const lastLeftToken = sourceCode.getTokenBefore(operatorToken);
             const firstRightToken = sourceCode.getTokenAfter(operatorToken);
-            const rightText = sourceCode
-                .getText()
-                .slice(firstRightToken.range[0], node.range[1]);
 
+            const source = sourceCode.getText();
+
+            const leftText = source.slice(
+                node.range[0],
+                lastLeftToken.range[1]
+            );
+            const textBeforeOperator = source.slice(
+                lastLeftToken.range[1],
+                operatorToken.range[0]
+            );
+            const textAfterOperator = source.slice(
+                operatorToken.range[1],
+                firstRightToken.range[0]
+            );
+            const rightText = source.slice(
+                firstRightToken.range[0],
+                node.range[1]
+            );
+
+            const tokenBefore = sourceCode.getTokenBefore(node);
+            const tokenAfter = sourceCode.getTokenAfter(node);
             let prefix = "";
+            let suffix = "";
 
             if (
                 tokenBefore &&
@@ -357,13 +305,22 @@ module.exports = {
                 prefix = " ";
             }
 
+            if (
+                tokenAfter &&
+                node.range[1] === tokenAfter.range[0] &&
+                !astUtils.canTokensBeAdjacent(lastLeftToken, tokenAfter)
+            ) {
+                suffix = " ";
+            }
+
             return (
                 prefix +
                 rightText +
                 textBeforeOperator +
                 OPERATOR_FLIP_MAP[operatorToken.value] +
                 textAfterOperator +
-                leftText
+                leftText +
+                suffix
             );
         }
 
diff --git a/eslint/lib/shared/config-ops.js b/eslint/lib/shared/config-ops.js
deleted file mode 100644 (file)
index 3b4d569..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-/**
- * @fileoverview Config file operations. This file must be usable in the browser,
- * so no Node-specific code can be here.
- * @author Nicholas C. Zakas
- */
-"use strict";
-
-//------------------------------------------------------------------------------
-// Private
-//------------------------------------------------------------------------------
-
-const RULE_SEVERITY_STRINGS = ["off", "warn", "error"],
-    RULE_SEVERITY = RULE_SEVERITY_STRINGS.reduce((map, value, index) => {
-        map[value] = index;
-        return map;
-    }, {}),
-    VALID_SEVERITIES = [0, 1, 2, "off", "warn", "error"];
-
-//------------------------------------------------------------------------------
-// Public Interface
-//------------------------------------------------------------------------------
-
-module.exports = {
-
-    /**
-     * Normalizes the severity value of a rule's configuration to a number
-     * @param {(number|string|[number, ...*]|[string, ...*])} ruleConfig A rule's configuration value, generally
-     * received from the user. A valid config value is either 0, 1, 2, the string "off" (treated the same as 0),
-     * the string "warn" (treated the same as 1), the string "error" (treated the same as 2), or an array
-     * whose first element is one of the above values. Strings are matched case-insensitively.
-     * @returns {(0|1|2)} The numeric severity value if the config value was valid, otherwise 0.
-     */
-    getRuleSeverity(ruleConfig) {
-        const severityValue = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig;
-
-        if (severityValue === 0 || severityValue === 1 || severityValue === 2) {
-            return severityValue;
-        }
-
-        if (typeof severityValue === "string") {
-            return RULE_SEVERITY[severityValue.toLowerCase()] || 0;
-        }
-
-        return 0;
-    },
-
-    /**
-     * Converts old-style severity settings (0, 1, 2) into new-style
-     * severity settings (off, warn, error) for all rules. Assumption is that severity
-     * values have already been validated as correct.
-     * @param {Object} config The config object to normalize.
-     * @returns {void}
-     */
-    normalizeToStrings(config) {
-
-        if (config.rules) {
-            Object.keys(config.rules).forEach(ruleId => {
-                const ruleConfig = config.rules[ruleId];
-
-                if (typeof ruleConfig === "number") {
-                    config.rules[ruleId] = RULE_SEVERITY_STRINGS[ruleConfig] || RULE_SEVERITY_STRINGS[0];
-                } else if (Array.isArray(ruleConfig) && typeof ruleConfig[0] === "number") {
-                    ruleConfig[0] = RULE_SEVERITY_STRINGS[ruleConfig[0]] || RULE_SEVERITY_STRINGS[0];
-                }
-            });
-        }
-    },
-
-    /**
-     * Determines if the severity for the given rule configuration represents an error.
-     * @param {int|string|Array} ruleConfig The configuration for an individual rule.
-     * @returns {boolean} True if the rule represents an error, false if not.
-     */
-    isErrorSeverity(ruleConfig) {
-        return module.exports.getRuleSeverity(ruleConfig) === 2;
-    },
-
-    /**
-     * Checks whether a given config has valid severity or not.
-     * @param {number|string|Array} ruleConfig The configuration for an individual rule.
-     * @returns {boolean} `true` if the configuration has valid severity.
-     */
-    isValidSeverity(ruleConfig) {
-        let severity = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig;
-
-        if (typeof severity === "string") {
-            severity = severity.toLowerCase();
-        }
-        return VALID_SEVERITIES.indexOf(severity) !== -1;
-    },
-
-    /**
-     * Checks whether every rule of a given config has valid severity or not.
-     * @param {Object} config The configuration for rules.
-     * @returns {boolean} `true` if the configuration has valid severity.
-     */
-    isEverySeverityValid(config) {
-        return Object.keys(config).every(ruleId => this.isValidSeverity(config[ruleId]));
-    },
-
-    /**
-     * Normalizes a value for a global in a config
-     * @param {(boolean|string|null)} configuredValue The value given for a global in configuration or in
-     * a global directive comment
-     * @returns {("readable"|"writeable"|"off")} The value normalized as a string
-     * @throws Error if global value is invalid
-     */
-    normalizeConfigGlobal(configuredValue) {
-        switch (configuredValue) {
-            case "off":
-                return "off";
-
-            case true:
-            case "true":
-            case "writeable":
-            case "writable":
-                return "writable";
-
-            case null:
-            case false:
-            case "false":
-            case "readable":
-            case "readonly":
-                return "readonly";
-
-            default:
-                throw new Error(`'${configuredValue}' is not a valid configuration for a global (use 'readonly', 'writable', or 'off')`);
-        }
-    }
-};
index 458bd2a802b95c142a7fd97b5874393493968d53..03b32f1c9183ed495a1e5428106a990f9e951b58 100644 (file)
@@ -1,3 +1,15 @@
+/*
+ * STOP!!! DO NOT MODIFY.
+ *
+ * This file is part of the ongoing work to move the eslintrc-style config
+ * system into the @eslint/eslintrc package. This file needs to remain
+ * unchanged in order for this work to proceed.
+ *
+ * If you think you need to change this file, please contact @nzakas first.
+ *
+ * Thanks in advance for your cooperation.
+ */
+
 /**
  * @fileoverview Validates configs.
  * @author Brandon Mills
@@ -12,9 +24,9 @@
 const
     util = require("util"),
     configSchema = require("../../conf/config-schema"),
-    BuiltInEnvironments = require("../../conf/environments"),
+    BuiltInEnvironments = require("@eslint/eslintrc/conf/environments"),
     BuiltInRules = require("../rules"),
-    ConfigOps = require("./config-ops"),
+    ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"),
     { emitDeprecationWarning } = require("./deprecation-warnings");
 
 const ajv = require("./ajv")();
diff --git a/eslint/lib/shared/naming.js b/eslint/lib/shared/naming.js
deleted file mode 100644 (file)
index 32cff94..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-/**
- * @fileoverview Common helpers for naming of plugins, formatters and configs
- */
-"use strict";
-
-const NAMESPACE_REGEX = /^@.*\//iu;
-
-/**
- * Brings package name to correct format based on prefix
- * @param {string} name The name of the package.
- * @param {string} prefix Can be either "eslint-plugin", "eslint-config" or "eslint-formatter"
- * @returns {string} Normalized name of the package
- * @private
- */
-function normalizePackageName(name, prefix) {
-    let normalizedName = name;
-
-    /**
-     * On Windows, name can come in with Windows slashes instead of Unix slashes.
-     * Normalize to Unix first to avoid errors later on.
-     * https://github.com/eslint/eslint/issues/5644
-     */
-    if (normalizedName.includes("\\")) {
-        normalizedName = normalizedName.replace(/\\/gu, "/");
-    }
-
-    if (normalizedName.charAt(0) === "@") {
-
-        /**
-         * it's a scoped package
-         * package name is the prefix, or just a username
-         */
-        const scopedPackageShortcutRegex = new RegExp(`^(@[^/]+)(?:/(?:${prefix})?)?$`, "u"),
-            scopedPackageNameRegex = new RegExp(`^${prefix}(-|$)`, "u");
-
-        if (scopedPackageShortcutRegex.test(normalizedName)) {
-            normalizedName = normalizedName.replace(scopedPackageShortcutRegex, `$1/${prefix}`);
-        } else if (!scopedPackageNameRegex.test(normalizedName.split("/")[1])) {
-
-            /**
-             * for scoped packages, insert the prefix after the first / unless
-             * the path is already @scope/eslint or @scope/eslint-xxx-yyy
-             */
-            normalizedName = normalizedName.replace(/^@([^/]+)\/(.*)$/u, `@$1/${prefix}-$2`);
-        }
-    } else if (!normalizedName.startsWith(`${prefix}-`)) {
-        normalizedName = `${prefix}-${normalizedName}`;
-    }
-
-    return normalizedName;
-}
-
-/**
- * Removes the prefix from a fullname.
- * @param {string} fullname The term which may have the prefix.
- * @param {string} prefix The prefix to remove.
- * @returns {string} The term without prefix.
- */
-function getShorthandName(fullname, prefix) {
-    if (fullname[0] === "@") {
-        let matchResult = new RegExp(`^(@[^/]+)/${prefix}$`, "u").exec(fullname);
-
-        if (matchResult) {
-            return matchResult[1];
-        }
-
-        matchResult = new RegExp(`^(@[^/]+)/${prefix}-(.+)$`, "u").exec(fullname);
-        if (matchResult) {
-            return `${matchResult[1]}/${matchResult[2]}`;
-        }
-    } else if (fullname.startsWith(`${prefix}-`)) {
-        return fullname.slice(prefix.length + 1);
-    }
-
-    return fullname;
-}
-
-/**
- * Gets the scope (namespace) of a term.
- * @param {string} term The term which may have the namespace.
- * @returns {string} The namespace of the term if it has one.
- */
-function getNamespaceFromTerm(term) {
-    const match = term.match(NAMESPACE_REGEX);
-
-    return match ? match[0] : "";
-}
-
-//------------------------------------------------------------------------------
-// Public Interface
-//------------------------------------------------------------------------------
-
-module.exports = {
-    normalizePackageName,
-    getShorthandName,
-    getNamespaceFromTerm
-};
index 80335c5cfca7c84c1dd83cbe33481ed8247682ec..cd743f3795b2daad89a2983c8d6382b882df18d3 100644 (file)
@@ -1,3 +1,15 @@
+/*
+ * STOP!!! DO NOT MODIFY.
+ *
+ * This file is part of the ongoing work to move the eslintrc-style config
+ * system into the @eslint/eslintrc package. This file needs to remain
+ * unchanged in order for this work to proceed.
+ *
+ * If you think you need to change this file, please contact @nzakas first.
+ *
+ * Thanks in advance for your cooperation.
+ */
+
 /**
  * Utility for resolving a module relative to another module
  * @author Teddy Katz
index bbd95d1b37862fd6af3d38610906b837cac80f0f..8ad3b1b64ce1e3c635ab7c8f9babe952b7bc119d 100644 (file)
@@ -21,7 +21,7 @@ module.exports = {};
 /**
  * @typedef {Object} ParserOptions
  * @property {EcmaFeatures} [ecmaFeatures] The optional features.
- * @property {3|5|6|7|8|9|10|11|2015|2016|2017|2018|2019|2020} [ecmaVersion] The ECMAScript version (or revision number).
+ * @property {3|5|6|7|8|9|10|11|12|2015|2016|2017|2018|2019|2020|2021} [ecmaVersion] The ECMAScript version (or revision number).
  * @property {"script"|"module"} [sourceType] The source code type.
  */
 
index f7c5f71ebe32568b474ece0b90377c262cf6d292..4defd7ac4d159d19d67e1bfe42df545fb963d958 100644 (file)
@@ -2,4 +2,4 @@ ESLint couldn't find the config "<%- configName %>" to extend from. Please check
 
 The config "<%- configName %>" was referenced from the config file in "<%- importerName %>".
 
-If you still have problems, please stop by https://eslint.org/chat to chat with the team.
+If you still have problems, please stop by https://eslint.org/chat/help to chat with the team.
index f1f7beb63b19a08a7500896897b76d7642678e32..b46a7e5a7a6f3f9c3374039451e9544913874919 100644 (file)
@@ -4,4 +4,4 @@ ESLint couldn't find a configuration file. To set up a configuration file for th
 
 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
+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
index f8b60631c58ea183ca928822326d1ad951b3b246..3ab4b340ef2dabfad7459c13bf9eea50c039707d 100644 (file)
@@ -4,4 +4,4 @@ ESLint couldn't determine the plugin "<%- pluginId %>" uniquely.
 
 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 to chat with the team.
+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.txt b/eslint/messages/plugin-invalid.txt
new file mode 100644 (file)
index 0000000..3ee2518
--- /dev/null
@@ -0,0 +1,8 @@
+"<%- 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.
index 3d376733085667662b70c01d246d0c4788be6730..aa25f59ac440ba84ef27d728cbd8865375d04946 100644 (file)
@@ -8,4 +8,4 @@ It's likely that the plugin isn't installed correctly. Try reinstalling by runni
 
 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 to chat with the team.
+If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
index 7d72149a8fd4fb030694719e5e19884f8ef6cb66..3eed1af58665a3cd0369b024fbf25cab5901d415 100644 (file)
@@ -1,3 +1,3 @@
 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 to chat with the team.
+If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.
index f9e204ce2a24f85115b7ae37eef52489e0b2d8ef..37c5b51f0088b27623a7b847bb425c04f467e7df 100644 (file)
@@ -1,6 +1,6 @@
 {
   "name": "eslint",
-  "version": "7.2.0",
+  "version": "7.12.1",
   "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
   "description": "An AST-based pattern checker for JavaScript.",
   "bin": {
   "bugs": "https://github.com/eslint/eslint/issues/",
   "dependencies": {
     "@babel/code-frame": "^7.0.0",
+    "@eslint/eslintrc": "^0.2.1",
     "ajv": "^6.10.0",
     "chalk": "^4.0.0",
     "cross-spawn": "^7.0.2",
     "debug": "^4.0.1",
     "doctrine": "^3.0.0",
-    "eslint-scope": "^5.1.0",
-    "eslint-utils": "^2.0.0",
-    "eslint-visitor-keys": "^1.2.0",
-    "espree": "^7.1.0",
+    "enquirer": "^2.3.5",
+    "eslint-scope": "^5.1.1",
+    "eslint-utils": "^2.1.0",
+    "eslint-visitor-keys": "^2.0.0",
+    "espree": "^7.3.0",
     "esquery": "^1.2.0",
     "esutils": "^2.0.2",
     "file-entry-cache": "^5.0.1",
     "ignore": "^4.0.6",
     "import-fresh": "^3.0.0",
     "imurmurhash": "^0.1.4",
-    "inquirer": "^7.0.0",
     "is-glob": "^4.0.0",
     "js-yaml": "^3.13.1",
     "json-stable-stringify-without-jsonify": "^1.0.1",
     "levn": "^0.4.1",
-    "lodash": "^4.17.14",
+    "lodash": "^4.17.19",
     "minimatch": "^3.0.4",
     "natural-compare": "^1.4.0",
     "optionator": "^0.9.1",
     "eslint-release": "^2.0.0",
     "eslump": "^2.0.0",
     "esprima": "^4.0.1",
+    "fs-teardown": "^0.1.0",
     "glob": "^7.1.6",
     "jsdoc": "^3.5.5",
     "karma": "^4.0.1",
     "karma-mocha": "^1.3.0",
     "karma-mocha-reporter": "^2.2.3",
     "karma-webpack": "^4.0.0-rc.6",
-    "leche": "^2.2.3",
     "lint-staged": "^10.1.2",
     "load-perf": "^0.2.0",
     "markdownlint": "^0.19.0",
     "npm-license": "^0.3.3",
     "nyc": "^15.0.1",
     "proxyquire": "^2.0.1",
-    "puppeteer": "^2.1.1",
+    "puppeteer": "^4.0.0",
     "recast": "^0.19.0",
     "regenerator-runtime": "^0.13.2",
     "shelljs": "^0.8.2",
index d31fcde74d3c6bfe70e620f8eff1104f19356361..bd3035b540eda0c920defb725fba246804020335 100644 (file)
@@ -4,7 +4,7 @@
 * **Node Version:**
 * **npm Version:**
 
-**What parser (default, Babel-ESLint, etc.) are you using?**
+**What parser (default, `@babel/eslint-parser`, `@typescript-eslint/parser`, etc.) are you using?**
 
 **Please show your full configuration:**
 
index a45820917f280d77366bc897650062be955beee5..ddacfc7bec6f1cffb2c21362f6696e36416a25dc 100644 (file)
@@ -60,11 +60,11 @@ const { Volume, createFsFromVolume } = require("memfs");
 const Proxyquire = require("proxyquire/lib/proxyquire");
 
 const CascadingConfigArrayFactoryPath =
-    require.resolve("../../lib/cli-engine/cascading-config-array-factory");
+    require.resolve("@eslint/eslintrc/lib/cascading-config-array-factory");
 const CLIEnginePath =
     require.resolve("../../lib/cli-engine/cli-engine");
 const ConfigArrayFactoryPath =
-    require.resolve("../../lib/cli-engine/config-array-factory");
+    require.resolve("@eslint/eslintrc/lib/config-array-factory");
 const FileEnumeratorPath =
     require.resolve("../../lib/cli-engine/file-enumerator");
 const LoadRulesPath =
index 0431e3aced4afd3369cda54ac6cf6ab3c4f8c0c4..7615613d25c21a2bb297521e494070d9d25ab515 100644 (file)
@@ -1,5 +1,13 @@
+/**
+ * @fileoverview Utilities used in tests
+ */
+
 "use strict";
 
+//-----------------------------------------------------------------------------
+// Requirements
+//-----------------------------------------------------------------------------
+
 const {
     defineInMemoryFs,
     defineConfigArrayFactoryWithInMemoryFileSystem,
@@ -9,6 +17,11 @@ const {
     defineESLintWithInMemoryFileSystem
 } = require("./in-memory-fs");
 
+const { createTeardown, addFile } = require("fs-teardown");
+
+//-----------------------------------------------------------------------------
+// Helpers
+//-----------------------------------------------------------------------------
 
 /**
  * Prevents leading spaces in a multiline template literal from appearing in the resulting string
@@ -27,6 +40,26 @@ function unIndent(strings, ...values) {
     return lines.map(line => line.slice(minLineIndent)).join("\n");
 }
 
+/**
+ * Creates a new filesystem volume at the given location with the given files.
+ * @param {Object} desc A description of the filesystem volume to create.
+ * @param {string} desc.cwd The current working directory ESLint is using.
+ * @param {Object} desc.files A map of filename to file contents to create.
+ * @returns {Teardown} An object with prepare(), cleanup(), and getPath()
+ *      methods.
+ */
+function createCustomTeardown({ cwd, files }) {
+    const { prepare, cleanup, getPath } = createTeardown(
+        cwd,
+        ...Object.keys(files).map(filename => addFile(filename, files[filename]))
+    );
+
+    return { prepare, cleanup, getPath };
+}
+
+//-----------------------------------------------------------------------------
+// Exports
+//-----------------------------------------------------------------------------
 
 module.exports = {
     unIndent,
@@ -35,5 +68,6 @@ module.exports = {
     defineCascadingConfigArrayFactoryWithInMemoryFileSystem,
     defineFileEnumeratorWithInMemoryFileSystem,
     defineCLIEngineWithInMemoryFileSystem,
-    defineESLintWithInMemoryFileSystem
+    defineESLintWithInMemoryFileSystem,
+    createCustomTeardown
 };
index cfc5128dc5725a728d0e75854449bc3629d7208b..7500d6611cee76f3fbe04284d242a92cb9c62cfa 100644 (file)
@@ -179,8 +179,8 @@ describe("bin/eslint.js", () => {
 
     describe("running on files", () => {
         it("has exit code 0 if no linting errors occur", () => assertExitCode(runESLint(["bin/eslint.js"]), 0));
-        it("has exit code 0 if a linting warning is reported", () => assertExitCode(runESLint(["bin/eslint.js", "--env", "es2020", "--no-eslintrc", "--rule", "semi: [1, never]"]), 0));
-        it("has exit code 1 if a linting error is reported", () => assertExitCode(runESLint(["bin/eslint.js", "--env", "es2020", "--no-eslintrc", "--rule", "semi: [2, never]"]), 1));
+        it("has exit code 0 if a linting warning is reported", () => assertExitCode(runESLint(["bin/eslint.js", "--env", "es2021", "--no-eslintrc", "--rule", "semi: [1, never]"]), 0));
+        it("has exit code 1 if a linting error is reported", () => assertExitCode(runESLint(["bin/eslint.js", "--env", "es2021", "--no-eslintrc", "--rule", "semi: [2, never]"]), 1));
         it("has exit code 1 if a syntax error is thrown", () => assertExitCode(runESLint(["README.md"]), 1));
     });
 
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--do-while-and.js b/eslint/tests/fixtures/code-path-analysis/assignment--do-while-and.js
new file mode 100644 (file)
index 0000000..3545211
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_2->s1_4;
+s1_3->s1_4->final;
+*/
+do {
+    foo();
+} while (a &&= b);
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nDoWhileStatement:enter"];
+    s1_2[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_4[label="DoWhileStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_2->s1_4;
+    s1_3->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--do-while-or.js b/eslint/tests/fixtures/code-path-analysis/assignment--do-while-or.js
new file mode 100644 (file)
index 0000000..6762c69
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_2->s1_2;
+s1_3->s1_4->final;
+*/
+do {
+    foo();
+} while (a ||= b);
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nDoWhileStatement:enter"];
+    s1_2[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_4[label="DoWhileStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_2->s1_2;
+    s1_3->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--do-while-qq.js b/eslint/tests/fixtures/code-path-analysis/assignment--do-while-qq.js
new file mode 100644 (file)
index 0000000..f35b620
--- /dev/null
@@ -0,0 +1,23 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_2->s1_2;
+s1_3->s1_4;
+s1_2->s1_4->final;
+*/
+do {
+    foo();
+} while (a ??= b);
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nDoWhileStatement:enter"];
+    s1_2[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_4[label="DoWhileStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_2->s1_2;
+    s1_3->s1_4;
+    s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--for-and-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--for-and-1.js
new file mode 100644 (file)
index 0000000..d135f6f
--- /dev/null
@@ -0,0 +1,23 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_2->s1_6;
+s1_3->s1_6->final;
+*/
+for (init; a &&= b; update) {
+    foo();
+}
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nForStatement:enter\nIdentifier (init)"];
+    s1_2[label="AssignmentExpression:enter\nIdentifier (a)"];
+    s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+    s1_5[label="Identifier (update)"];
+    s1_6[label="ForStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_2->s1_6;
+    s1_3->s1_6->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--for-and-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--for-and-2.js
new file mode 100644 (file)
index 0000000..7f373b0
--- /dev/null
@@ -0,0 +1,22 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_5;
+s1_3->s1_5->final;
+*/
+for (init; a &&= b;) {
+    foo();
+}
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nForStatement:enter\nIdentifier (init)"];
+    s1_2[label="AssignmentExpression:enter\nIdentifier (a)"];
+    s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+    s1_5[label="ForStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_5;
+    s1_3->s1_5->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--for-or-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--for-or-1.js
new file mode 100644 (file)
index 0000000..e04977a
--- /dev/null
@@ -0,0 +1,23 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_2->s1_4;
+s1_3->s1_6->final;
+*/
+for (init; a ||= b; update) {
+    foo();
+}
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nForStatement:enter\nIdentifier (init)"];
+    s1_2[label="AssignmentExpression:enter\nIdentifier (a)"];
+    s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+    s1_5[label="Identifier (update)"];
+    s1_6[label="ForStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_2->s1_4;
+    s1_3->s1_6->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--for-or-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--for-or-2.js
new file mode 100644 (file)
index 0000000..58648a4
--- /dev/null
@@ -0,0 +1,22 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_4;
+s1_3->s1_5->final;
+*/
+for (init; a ||= b;) {
+    foo();
+}
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nForStatement:enter\nIdentifier (init)"];
+    s1_2[label="AssignmentExpression:enter\nIdentifier (a)"];
+    s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+    s1_5[label="ForStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_4;
+    s1_3->s1_5->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--for-qq-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--for-qq-1.js
new file mode 100644 (file)
index 0000000..85efdc3
--- /dev/null
@@ -0,0 +1,25 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_2->s1_4;
+s1_3->s1_6;
+s1_2->s1_6->final;
+*/
+for (init; a ??= b; update) {
+    foo();
+}
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nForStatement:enter\nIdentifier (init)"];
+    s1_2[label="AssignmentExpression:enter\nIdentifier (a)"];
+    s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+    s1_5[label="Identifier (update)"];
+    s1_6[label="ForStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_2->s1_4;
+    s1_3->s1_6;
+    s1_2->s1_6->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--for-qq-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--for-qq-2.js
new file mode 100644 (file)
index 0000000..cf5d31f
--- /dev/null
@@ -0,0 +1,24 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_4;
+s1_3->s1_5;
+s1_2->s1_5->final;
+*/
+for (init; a ??= b;) {
+    foo();
+}
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nForStatement:enter\nIdentifier (init)"];
+    s1_2[label="AssignmentExpression:enter\nIdentifier (a)"];
+    s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+    s1_5[label="ForStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_4;
+    s1_3->s1_5;
+    s1_2->s1_5->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--if-and-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--if-and-1.js
new file mode 100644 (file)
index 0000000..d16b407
--- /dev/null
@@ -0,0 +1,23 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+if (a &&= b) {
+    foo();
+}
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nIfStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_3[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+    s1_4[label="IfStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_4;
+    s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--if-and-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--if-and-2.js
new file mode 100644 (file)
index 0000000..cbf1892
--- /dev/null
@@ -0,0 +1,28 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_5;
+s1_1->s1_4->s1_5;
+s1_2->s1_4;
+s1_5->final;
+*/
+if (a &&= b) {
+    foo();
+} else {
+    bar();
+}
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nIfStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_3[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+    s1_5[label="IfStatement:exit\nProgram:exit"];
+    s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (bar)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+    initial->s1_1->s1_2->s1_3->s1_5;
+    s1_1->s1_4->s1_5;
+    s1_2->s1_4;
+    s1_5->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--if-and-3.js b/eslint/tests/fixtures/code-path-analysis/assignment--if-and-3.js
new file mode 100644 (file)
index 0000000..ad6c187
--- /dev/null
@@ -0,0 +1,36 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_7->s1_9;
+s1_1->s1_8->s1_9;
+s1_2->s1_8;
+s1_3->s1_8;
+s1_4->s1_6->s1_7;
+s1_9->final;
+*/
+if ((a &&= b) && c) {
+    d ? foo() : bar();
+} else {
+    baz();
+}
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nIfStatement:enter\nLogicalExpression:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_3[label="Identifier (c)\nLogicalExpression:exit"];
+    s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nConditionalExpression:enter\nIdentifier (d)"];
+    s1_5[label="CallExpression:enter\nIdentifier (foo)\nCallExpression:exit"];
+    s1_7[label="ConditionalExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+    s1_9[label="IfStatement:exit\nProgram:exit"];
+    s1_8[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (baz)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+    s1_6[label="CallExpression:enter\nIdentifier (bar)\nCallExpression:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_7->s1_9;
+    s1_1->s1_8->s1_9;
+    s1_2->s1_8;
+    s1_3->s1_8;
+    s1_4->s1_6->s1_7;
+    s1_9->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--if-or-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--if-or-1.js
new file mode 100644 (file)
index 0000000..1a2bd7d
--- /dev/null
@@ -0,0 +1,23 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_3;
+s1_2->s1_4->final;
+*/
+if (a ||= b) {
+    foo();
+}
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nIfStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_3[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+    s1_4[label="IfStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_3;
+    s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--if-or-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--if-or-2.js
new file mode 100644 (file)
index 0000000..628ef60
--- /dev/null
@@ -0,0 +1,26 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_5;
+s1_1->s1_3;
+s1_2->s1_4->s1_5->final;
+*/
+if (a ||= b) {
+    foo();
+} else {
+    bar();
+}
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nIfStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_3[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+    s1_5[label="IfStatement:exit\nProgram:exit"];
+    s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (bar)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+    initial->s1_1->s1_2->s1_3->s1_5;
+    s1_1->s1_3;
+    s1_2->s1_4->s1_5->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--if-or-3.js b/eslint/tests/fixtures/code-path-analysis/assignment--if-or-3.js
new file mode 100644 (file)
index 0000000..cfef916
--- /dev/null
@@ -0,0 +1,36 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_7->s1_9;
+s1_1->s1_4;
+s1_2->s1_4;
+s1_3->s1_8->s1_9;
+s1_4->s1_6->s1_7;
+s1_9->final;
+*/
+if ((a ||= b) || c) {
+    d ? foo() : bar();
+} else {
+    baz();
+}
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nIfStatement:enter\nLogicalExpression:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_3[label="Identifier (c)\nLogicalExpression:exit"];
+    s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nConditionalExpression:enter\nIdentifier (d)"];
+    s1_5[label="CallExpression:enter\nIdentifier (foo)\nCallExpression:exit"];
+    s1_7[label="ConditionalExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+    s1_9[label="IfStatement:exit\nProgram:exit"];
+    s1_8[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (baz)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+    s1_6[label="CallExpression:enter\nIdentifier (bar)\nCallExpression:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_7->s1_9;
+    s1_1->s1_4;
+    s1_2->s1_4;
+    s1_3->s1_8->s1_9;
+    s1_4->s1_6->s1_7;
+    s1_9->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--if-qq-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--if-qq-1.js
new file mode 100644 (file)
index 0000000..847ea79
--- /dev/null
@@ -0,0 +1,25 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_3;
+s1_2->s1_4;
+s1_1->s1_4->final;
+*/
+if (a ??= b) {
+    foo();
+}
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nIfStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_3[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+    s1_4[label="IfStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_3;
+    s1_2->s1_4;
+    s1_1->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--if-qq-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--if-qq-2.js
new file mode 100644 (file)
index 0000000..50b2fdb
--- /dev/null
@@ -0,0 +1,30 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_5;
+s1_1->s1_3;
+s1_2->s1_4->s1_5;
+s1_1->s1_4;
+s1_5->final;
+*/
+if (a ??= b) {
+    foo();
+} else {
+    bar();
+}
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nIfStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_3[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+    s1_5[label="IfStatement:exit\nProgram:exit"];
+    s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (bar)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+    initial->s1_1->s1_2->s1_3->s1_5;
+    s1_1->s1_3;
+    s1_2->s1_4->s1_5;
+    s1_1->s1_4;
+    s1_5->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--logical-and-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--logical-and-1.js
new file mode 100644 (file)
index 0000000..f8d4b44
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a &&= b && c;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="LogicalExpression:enter\nIdentifier (b)"];
+    s1_3[label="Identifier (c)\nLogicalExpression:exit"];
+    s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_4;
+    s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--logical-and-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--logical-and-2.js
new file mode 100644 (file)
index 0000000..2ad1da1
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a &&= b || c;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="LogicalExpression:enter\nIdentifier (b)"];
+    s1_3[label="Identifier (c)\nLogicalExpression:exit"];
+    s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_4;
+    s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--logical-and-3.js b/eslint/tests/fixtures/code-path-analysis/assignment--logical-and-3.js
new file mode 100644 (file)
index 0000000..ff80195
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a &&= b ?? c;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="LogicalExpression:enter\nIdentifier (b)"];
+    s1_3[label="Identifier (c)\nLogicalExpression:exit"];
+    s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_4;
+    s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--logical-or-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--logical-or-1.js
new file mode 100644 (file)
index 0000000..8f51a6e
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a ||= b && c;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="LogicalExpression:enter\nIdentifier (b)"];
+    s1_3[label="Identifier (c)\nLogicalExpression:exit"];
+    s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_4;
+    s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--logical-or-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--logical-or-2.js
new file mode 100644 (file)
index 0000000..7878072
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a ||= b || c;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="LogicalExpression:enter\nIdentifier (b)"];
+    s1_3[label="Identifier (c)\nLogicalExpression:exit"];
+    s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_4;
+    s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--logical-or-3.js b/eslint/tests/fixtures/code-path-analysis/assignment--logical-or-3.js
new file mode 100644 (file)
index 0000000..52498d1
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a ||= b ?? c;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="LogicalExpression:enter\nIdentifier (b)"];
+    s1_3[label="Identifier (c)\nLogicalExpression:exit"];
+    s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_4;
+    s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--logical-qq-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--logical-qq-1.js
new file mode 100644 (file)
index 0000000..8b2873c
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a ??= b && c;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="LogicalExpression:enter\nIdentifier (b)"];
+    s1_3[label="Identifier (c)\nLogicalExpression:exit"];
+    s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_4;
+    s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--logical-qq-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--logical-qq-2.js
new file mode 100644 (file)
index 0000000..dc17fb4
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a ??= b || c;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="LogicalExpression:enter\nIdentifier (b)"];
+    s1_3[label="Identifier (c)\nLogicalExpression:exit"];
+    s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_4;
+    s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--logical-qq-3.js b/eslint/tests/fixtures/code-path-analysis/assignment--logical-qq-3.js
new file mode 100644 (file)
index 0000000..6baaeec
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a ??= b ?? c;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="LogicalExpression:enter\nIdentifier (b)"];
+    s1_3[label="Identifier (c)\nLogicalExpression:exit"];
+    s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_4;
+    s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--multi-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--multi-1.js
new file mode 100644 (file)
index 0000000..fd7300d
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a &&= b &&= c;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="AssignmentExpression:enter\nIdentifier (b)"];
+    s1_3[label="Identifier (c)\nAssignmentExpression:exit"];
+    s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_4;
+    s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--multi-10.js b/eslint/tests/fixtures/code-path-analysis/assignment--multi-10.js
new file mode 100644 (file)
index 0000000..dc4785c
--- /dev/null
@@ -0,0 +1,28 @@
+/*expected
+initial->s1_1->s1_2->s1_4->s1_5->s1_6->s1_8->s1_9;
+s1_1->s1_3->s1_4->s1_9;
+s1_5->s1_7->s1_8;
+s1_9->final;
+*/
+a[b ? c : d] ||= e[f ? g : h] = i;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nIdentifier (a)\nConditionalExpression:enter\nIdentifier (b)"];
+    s1_2[label="Identifier (c)"];
+    s1_4[label="ConditionalExpression:exit\nMemberExpression:exit"];
+    s1_5[label="AssignmentExpression:enter\nMemberExpression:enter\nIdentifier (e)\nConditionalExpression:enter\nIdentifier (f)"];
+    s1_6[label="Identifier (g)"];
+    s1_8[label="ConditionalExpression:exit\nMemberExpression:exit\nIdentifier (i)\nAssignmentExpression:exit"];
+    s1_9[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    s1_3[label="Identifier (d)"];
+    s1_7[label="Identifier (h)"];
+    initial->s1_1->s1_2->s1_4->s1_5->s1_6->s1_8->s1_9;
+    s1_1->s1_3->s1_4->s1_9;
+    s1_5->s1_7->s1_8;
+    s1_9->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--multi-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--multi-2.js
new file mode 100644 (file)
index 0000000..4267d06
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a &&= b ||= c;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="AssignmentExpression:enter\nIdentifier (b)"];
+    s1_3[label="Identifier (c)\nAssignmentExpression:exit"];
+    s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_4;
+    s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--multi-3.js b/eslint/tests/fixtures/code-path-analysis/assignment--multi-3.js
new file mode 100644 (file)
index 0000000..00befa1
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a ||= b ||= c;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="AssignmentExpression:enter\nIdentifier (b)"];
+    s1_3[label="Identifier (c)\nAssignmentExpression:exit"];
+    s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_4;
+    s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--multi-4.js b/eslint/tests/fixtures/code-path-analysis/assignment--multi-4.js
new file mode 100644 (file)
index 0000000..ef6e429
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a ||= b ??= c;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="AssignmentExpression:enter\nIdentifier (b)"];
+    s1_3[label="Identifier (c)\nAssignmentExpression:exit"];
+    s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_4;
+    s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--multi-5.js b/eslint/tests/fixtures/code-path-analysis/assignment--multi-5.js
new file mode 100644 (file)
index 0000000..084fd01
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a ??= b ??= c;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="AssignmentExpression:enter\nIdentifier (b)"];
+    s1_3[label="Identifier (c)\nAssignmentExpression:exit"];
+    s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_4;
+    s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--multi-6.js b/eslint/tests/fixtures/code-path-analysis/assignment--multi-6.js
new file mode 100644 (file)
index 0000000..b884186
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+a ??= b &&= c;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="AssignmentExpression:enter\nIdentifier (b)"];
+    s1_3[label="Identifier (c)\nAssignmentExpression:exit"];
+    s1_4[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_4;
+    s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--multi-7.js b/eslint/tests/fixtures/code-path-analysis/assignment--multi-7.js
new file mode 100644 (file)
index 0000000..1deb49c
--- /dev/null
@@ -0,0 +1,24 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_5;
+s1_1->s1_5;
+s1_2->s1_5;
+s1_3->s1_5->final;
+*/
+a ??= b ||= c &&= d;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="AssignmentExpression:enter\nIdentifier (b)"];
+    s1_3[label="AssignmentExpression:enter\nIdentifier (c)"];
+    s1_4[label="Identifier (d)\nAssignmentExpression:exit\nAssignmentExpression:exit"];
+    s1_5[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4->s1_5;
+    s1_1->s1_5;
+    s1_2->s1_5;
+    s1_3->s1_5->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--multi-8.js b/eslint/tests/fixtures/code-path-analysis/assignment--multi-8.js
new file mode 100644 (file)
index 0000000..6dad336
--- /dev/null
@@ -0,0 +1,14 @@
+/*expected
+initial->s1_1->final;
+*/
+a = b = c;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)\nAssignmentExpression:enter\nIdentifier (b)\nIdentifier (c)\nAssignmentExpression:exit\nAssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--multi-9.js b/eslint/tests/fixtures/code-path-analysis/assignment--multi-9.js
new file mode 100644 (file)
index 0000000..679eecc
--- /dev/null
@@ -0,0 +1,24 @@
+/*expected
+initial->s1_1->s1_2->s1_4->s1_5->s1_7->s1_8->s1_9;
+s1_1->s1_3->s1_4->s1_6->s1_7->s1_9->final;
+*/
+a[b ? c : d] = e[f ? g : h] ||= i;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nIdentifier (a)\nConditionalExpression:enter\nIdentifier (b)"];
+    s1_2[label="Identifier (c)"];
+    s1_4[label="ConditionalExpression:exit\nMemberExpression:exit\nAssignmentExpression:enter\nMemberExpression:enter\nIdentifier (e)\nConditionalExpression:enter\nIdentifier (f)"];
+    s1_5[label="Identifier (g)"];
+    s1_7[label="ConditionalExpression:exit\nMemberExpression:exit"];
+    s1_8[label="Identifier (i)"];
+    s1_9[label="AssignmentExpression:exit\nAssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    s1_3[label="Identifier (d)"];
+    s1_6[label="Identifier (h)"];
+    initial->s1_1->s1_2->s1_4->s1_5->s1_7->s1_8->s1_9;
+    s1_1->s1_3->s1_4->s1_6->s1_7->s1_9->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--nested-and-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--nested-and-1.js
new file mode 100644 (file)
index 0000000..c80d582
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+(a &&= b) && c;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nLogicalExpression:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_3[label="Identifier (c)"];
+    s1_4[label="LogicalExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_4;
+    s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--nested-and-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--nested-and-2.js
new file mode 100644 (file)
index 0000000..2cfdcc3
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_3;
+s1_2->s1_4->final;
+*/
+(a &&= b) || c;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nLogicalExpression:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_3[label="Identifier (c)"];
+    s1_4[label="LogicalExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_3;
+    s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--nested-and-3.js b/eslint/tests/fixtures/code-path-analysis/assignment--nested-and-3.js
new file mode 100644 (file)
index 0000000..8bb52f0
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+(a &&= b) ?? c;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nLogicalExpression:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_3[label="Identifier (c)"];
+    s1_4[label="LogicalExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_4;
+    s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--nested-or-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--nested-or-1.js
new file mode 100644 (file)
index 0000000..e0ec2e8
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_3;
+s1_2->s1_4->final;
+*/
+(a ||= b) && c;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nLogicalExpression:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_3[label="Identifier (c)"];
+    s1_4[label="LogicalExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_3;
+    s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--nested-or-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--nested-or-2.js
new file mode 100644 (file)
index 0000000..8398352
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+(a ||= b) || c;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nLogicalExpression:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_3[label="Identifier (c)"];
+    s1_4[label="LogicalExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_4;
+    s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--nested-or-3.js b/eslint/tests/fixtures/code-path-analysis/assignment--nested-or-3.js
new file mode 100644 (file)
index 0000000..a5cb26c
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+(a ||= b) ?? c;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nLogicalExpression:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_3[label="Identifier (c)"];
+    s1_4[label="LogicalExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_4;
+    s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--nested-qq-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--nested-qq-1.js
new file mode 100644 (file)
index 0000000..65d178b
--- /dev/null
@@ -0,0 +1,23 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_3;
+s1_2->s1_4;
+s1_1->s1_4->final;
+*/
+(a ??= b) && c;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nLogicalExpression:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_3[label="Identifier (c)"];
+    s1_4[label="LogicalExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_3;
+    s1_2->s1_4;
+    s1_1->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--nested-qq-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--nested-qq-2.js
new file mode 100644 (file)
index 0000000..1e8eeac
--- /dev/null
@@ -0,0 +1,23 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_3;
+s1_2->s1_4;
+s1_1->s1_4->final;
+*/
+(a ??= b) || c;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nLogicalExpression:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_3[label="Identifier (c)"];
+    s1_4[label="LogicalExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_3;
+    s1_2->s1_4;
+    s1_1->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--nested-qq-3.js b/eslint/tests/fixtures/code-path-analysis/assignment--nested-qq-3.js
new file mode 100644 (file)
index 0000000..ab94d6b
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4;
+s1_1->s1_4;
+s1_2->s1_4->final;
+*/
+(a ??= b) ?? c;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nLogicalExpression:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_3[label="Identifier (c)"];
+    s1_4[label="LogicalExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4;
+    s1_1->s1_4;
+    s1_2->s1_4->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--simple-and-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--simple-and-1.js
new file mode 100644 (file)
index 0000000..90e49db
--- /dev/null
@@ -0,0 +1,18 @@
+/*expected
+initial->s1_1->s1_2->s1_3;
+s1_1->s1_3->final;
+*/
+a &&= b;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="Identifier (b)"];
+    s1_3[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3;
+    s1_1->s1_3->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--simple-and-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--simple-and-2.js
new file mode 100644 (file)
index 0000000..046559a
--- /dev/null
@@ -0,0 +1,25 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_5->s1_6;
+s1_1->s1_6;
+s1_2->s1_4->s1_5;
+s1_6->final;
+*/
+a &&= b ? c : d;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="ConditionalExpression:enter\nIdentifier (b)"];
+    s1_3[label="Identifier (c)"];
+    s1_5[label="ConditionalExpression:exit"];
+    s1_6[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    s1_4[label="Identifier (d)"];
+    initial->s1_1->s1_2->s1_3->s1_5->s1_6;
+    s1_1->s1_6;
+    s1_2->s1_4->s1_5;
+    s1_6->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--simple-and-3.js b/eslint/tests/fixtures/code-path-analysis/assignment--simple-and-3.js
new file mode 100644 (file)
index 0000000..cd3d0ec
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_4->s1_5->s1_6;
+s1_1->s1_3->s1_4->s1_6->final;
+*/
+a[b ? c : d] &&= e;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nIdentifier (a)\nConditionalExpression:enter\nIdentifier (b)"];
+    s1_2[label="Identifier (c)"];
+    s1_4[label="ConditionalExpression:exit\nMemberExpression:exit"];
+    s1_5[label="Identifier (e)"];
+    s1_6[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    s1_3[label="Identifier (d)"];
+    initial->s1_1->s1_2->s1_4->s1_5->s1_6;
+    s1_1->s1_3->s1_4->s1_6->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--simple-bitand.js b/eslint/tests/fixtures/code-path-analysis/assignment--simple-bitand.js
new file mode 100644 (file)
index 0000000..e769969
--- /dev/null
@@ -0,0 +1,14 @@
+/*expected
+initial->s1_1->final;
+*/
+a &= b;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)\nIdentifier (b)\nAssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--simple-eq.js b/eslint/tests/fixtures/code-path-analysis/assignment--simple-eq.js
new file mode 100644 (file)
index 0000000..0022ada
--- /dev/null
@@ -0,0 +1,14 @@
+/*expected
+initial->s1_1->final;
+*/
+a = b;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)\nIdentifier (b)\nAssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--simple-or-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--simple-or-1.js
new file mode 100644 (file)
index 0000000..8737eaa
--- /dev/null
@@ -0,0 +1,18 @@
+/*expected
+initial->s1_1->s1_2->s1_3;
+s1_1->s1_3->final;
+*/
+a ||= b;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="Identifier (b)"];
+    s1_3[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3;
+    s1_1->s1_3->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--simple-or-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--simple-or-2.js
new file mode 100644 (file)
index 0000000..57f2702
--- /dev/null
@@ -0,0 +1,25 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_5->s1_6;
+s1_1->s1_6;
+s1_2->s1_4->s1_5;
+s1_6->final;
+*/
+a ||= b ? c : d;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="ConditionalExpression:enter\nIdentifier (b)"];
+    s1_3[label="Identifier (c)"];
+    s1_5[label="ConditionalExpression:exit"];
+    s1_6[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    s1_4[label="Identifier (d)"];
+    initial->s1_1->s1_2->s1_3->s1_5->s1_6;
+    s1_1->s1_6;
+    s1_2->s1_4->s1_5;
+    s1_6->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--simple-or-3.js b/eslint/tests/fixtures/code-path-analysis/assignment--simple-or-3.js
new file mode 100644 (file)
index 0000000..9ccc0de
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_4->s1_5->s1_6;
+s1_1->s1_3->s1_4->s1_6->final;
+*/
+a[b ? c : d] ||= e;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nIdentifier (a)\nConditionalExpression:enter\nIdentifier (b)"];
+    s1_2[label="Identifier (c)"];
+    s1_4[label="ConditionalExpression:exit\nMemberExpression:exit"];
+    s1_5[label="Identifier (e)"];
+    s1_6[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    s1_3[label="Identifier (d)"];
+    initial->s1_1->s1_2->s1_4->s1_5->s1_6;
+    s1_1->s1_3->s1_4->s1_6->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--simple-plus.js b/eslint/tests/fixtures/code-path-analysis/assignment--simple-plus.js
new file mode 100644 (file)
index 0000000..1237039
--- /dev/null
@@ -0,0 +1,14 @@
+/*expected
+initial->s1_1->final;
+*/
+a += b;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)\nIdentifier (b)\nAssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--simple-qq-1.js b/eslint/tests/fixtures/code-path-analysis/assignment--simple-qq-1.js
new file mode 100644 (file)
index 0000000..8ceede3
--- /dev/null
@@ -0,0 +1,18 @@
+/*expected
+initial->s1_1->s1_2->s1_3;
+s1_1->s1_3->final;
+*/
+a ??= b;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="Identifier (b)"];
+    s1_3[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3;
+    s1_1->s1_3->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--simple-qq-2.js b/eslint/tests/fixtures/code-path-analysis/assignment--simple-qq-2.js
new file mode 100644 (file)
index 0000000..be5276a
--- /dev/null
@@ -0,0 +1,25 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_5->s1_6;
+s1_1->s1_6;
+s1_2->s1_4->s1_5;
+s1_6->final;
+*/
+a ??= b ? c : d;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (a)"];
+    s1_2[label="ConditionalExpression:enter\nIdentifier (b)"];
+    s1_3[label="Identifier (c)"];
+    s1_5[label="ConditionalExpression:exit"];
+    s1_6[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    s1_4[label="Identifier (d)"];
+    initial->s1_1->s1_2->s1_3->s1_5->s1_6;
+    s1_1->s1_6;
+    s1_2->s1_4->s1_5;
+    s1_6->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--simple-qq-3.js b/eslint/tests/fixtures/code-path-analysis/assignment--simple-qq-3.js
new file mode 100644 (file)
index 0000000..bdf7fd8
--- /dev/null
@@ -0,0 +1,21 @@
+/*expected
+initial->s1_1->s1_2->s1_4->s1_5->s1_6;
+s1_1->s1_3->s1_4->s1_6->final;
+*/
+a[b ? c : d] ??= e;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nIdentifier (a)\nConditionalExpression:enter\nIdentifier (b)"];
+    s1_2[label="Identifier (c)"];
+    s1_4[label="ConditionalExpression:exit\nMemberExpression:exit"];
+    s1_5[label="Identifier (e)"];
+    s1_6[label="AssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    s1_3[label="Identifier (d)"];
+    initial->s1_1->s1_2->s1_4->s1_5->s1_6;
+    s1_1->s1_3->s1_4->s1_6->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--while-and.js b/eslint/tests/fixtures/code-path-analysis/assignment--while-and.js
new file mode 100644 (file)
index 0000000..8cc5e45
--- /dev/null
@@ -0,0 +1,22 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_5;
+s1_3->s1_5->final;
+*/
+while (a &&= b) {
+    foo();
+}
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nWhileStatement:enter"];
+    s1_2[label="AssignmentExpression:enter\nIdentifier (a)"];
+    s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+    s1_5[label="WhileStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_5;
+    s1_3->s1_5->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--while-or.js b/eslint/tests/fixtures/code-path-analysis/assignment--while-or.js
new file mode 100644 (file)
index 0000000..cde21d5
--- /dev/null
@@ -0,0 +1,22 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_4;
+s1_3->s1_5->final;
+*/
+while (a ||= b) {
+    foo();
+}
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nWhileStatement:enter"];
+    s1_2[label="AssignmentExpression:enter\nIdentifier (a)"];
+    s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+    s1_5[label="WhileStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_4;
+    s1_3->s1_5->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/assignment--while-qq.js b/eslint/tests/fixtures/code-path-analysis/assignment--while-qq.js
new file mode 100644 (file)
index 0000000..39f3184
--- /dev/null
@@ -0,0 +1,24 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_4;
+s1_3->s1_5;
+s1_2->s1_5->final;
+*/
+while (a ??= b) {
+    foo();
+}
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nWhileStatement:enter"];
+    s1_2[label="AssignmentExpression:enter\nIdentifier (a)"];
+    s1_3[label="Identifier (b)\nAssignmentExpression:exit"];
+    s1_4[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+    s1_5[label="WhileStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_4;
+    s1_3->s1_5;
+    s1_2->s1_5->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/logical--if-qdot-1.js b/eslint/tests/fixtures/code-path-analysis/logical--if-qdot-1.js
new file mode 100644 (file)
index 0000000..4e1d7b0
--- /dev/null
@@ -0,0 +1,38 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_6->s1_7->s1_9->s1_11;
+s1_1->s1_3->s1_10->s1_11;
+s1_4->s1_6->s1_8->s1_9;
+s1_11->final;
+*/
+if (obj?.foo) {
+    if (obj?.bar) {
+        foo();
+    } else {
+        bar();
+    }
+} else {
+    qiz();
+}
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nIfStatement:enter\nChainExpression:enter\nMemberExpression:enter\nIdentifier (obj)"];
+    s1_2[label="Identifier (foo)\nMemberExpression:exit"];
+    s1_3[label="ChainExpression:exit"];
+    s1_4[label="BlockStatement:enter\nIfStatement:enter\nChainExpression:enter\nMemberExpression:enter\nIdentifier (obj)"];
+    s1_5[label="Identifier (bar)\nMemberExpression:exit"];
+    s1_6[label="ChainExpression:exit"];
+    s1_7[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];        
+    s1_9[label="IfStatement:exit\nBlockStatement:exit"];
+    s1_11[label="IfStatement:exit\nProgram:exit"];
+    s1_10[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (qiz)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];       
+    s1_8[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (bar)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];        
+    initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_6->s1_7->s1_9->s1_11;
+    s1_1->s1_3->s1_10->s1_11;
+    s1_4->s1_6->s1_8->s1_9;
+    s1_11->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/optional-chaining--complex-1.js b/eslint/tests/fixtures/code-path-analysis/optional-chaining--complex-1.js
new file mode 100644 (file)
index 0000000..5444269
--- /dev/null
@@ -0,0 +1,38 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_5->s1_6->s1_7->s1_8->s1_9->s1_10->s1_11->s1_12->s1_13->s1_16;
+s1_1->s1_16;
+s1_2->s1_4->s1_5->s1_16;
+s1_6->s1_8->s1_16;
+s1_9->s1_11->s1_13;
+s1_16->final;
+*/
+
+obj?.[cond ? k1 : k2]?.[k3 || k4]?.(a1 && a2, b1 ?? b2).foo(arg)
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nChainExpression:enter\nCallExpression:enter\nMemberExpression:enter\nCallExpression:enter\nMemberExpression:enter\nMemberExpression:enter\nIdentifier (obj)"];
+    s1_2[label="ConditionalExpression:enter\nIdentifier (cond)"];
+    s1_3[label="Identifier (k1)"];
+    s1_5[label="ConditionalExpression:exit\nMemberExpression:exit"];
+    s1_6[label="LogicalExpression:enter\nIdentifier (k3)"];
+    s1_7[label="Identifier (k4)"];
+    s1_8[label="LogicalExpression:exit\nMemberExpression:exit"];
+    s1_9[label="LogicalExpression:enter\nIdentifier (a1)"];
+    s1_10[label="Identifier (a2)"];
+    s1_11[label="LogicalExpression:exit\nLogicalExpression:enter\nIdentifier (b1)"];
+    s1_12[label="Identifier (b2)"];
+    s1_13[label="LogicalExpression:exit\nCallExpression:exit\nIdentifier (foo)\nMemberExpression:exit\nIdentifier (arg)\nCallExpression:exit"];
+    s1_16[label="ChainExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    s1_4[label="Identifier (k2)"];
+    initial->s1_1->s1_2->s1_3->s1_5->s1_6->s1_7->s1_8->s1_9->s1_10->s1_11->s1_12->s1_13->s1_16;
+    s1_1->s1_16;
+    s1_2->s1_4->s1_5->s1_16;
+    s1_6->s1_8->s1_16;
+    s1_9->s1_11->s1_13;
+    s1_16->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/optional-chaining--complex-2.js b/eslint/tests/fixtures/code-path-analysis/optional-chaining--complex-2.js
new file mode 100644 (file)
index 0000000..8d53c07
--- /dev/null
@@ -0,0 +1,38 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_5->s1_6->s1_7->s1_8->s1_9->s1_10->s1_11->s1_12->s1_13->s1_16;
+s1_1->s1_16;
+s1_2->s1_4->s1_5->s1_16;
+s1_6->s1_8->s1_16;
+s1_9->s1_11->s1_13;
+s1_16->final;
+*/
+
+(obj?.[cond ? k1 : k2]?.[k3 || k4]?.(a1 && a2, b1 ?? b2)).foo(arg)
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nCallExpression:enter\nMemberExpression:enter\nChainExpression:enter\nCallExpression:enter\nMemberExpression:enter\nMemberExpression:enter\nIdentifier (obj)"];
+    s1_2[label="ConditionalExpression:enter\nIdentifier (cond)"];
+    s1_3[label="Identifier (k1)"];
+    s1_5[label="ConditionalExpression:exit\nMemberExpression:exit"];
+    s1_6[label="LogicalExpression:enter\nIdentifier (k3)"];
+    s1_7[label="Identifier (k4)"];
+    s1_8[label="LogicalExpression:exit\nMemberExpression:exit"];
+    s1_9[label="LogicalExpression:enter\nIdentifier (a1)"];
+    s1_10[label="Identifier (a2)"];
+    s1_11[label="LogicalExpression:exit\nLogicalExpression:enter\nIdentifier (b1)"];
+    s1_12[label="Identifier (b2)"];
+    s1_13[label="LogicalExpression:exit\nCallExpression:exit"];
+    s1_16[label="ChainExpression:exit\nIdentifier (foo)\nMemberExpression:exit\nIdentifier (arg)\nCallExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    s1_4[label="Identifier (k2)"];
+    initial->s1_1->s1_2->s1_3->s1_5->s1_6->s1_7->s1_8->s1_9->s1_10->s1_11->s1_12->s1_13->s1_16;
+    s1_1->s1_16;
+    s1_2->s1_4->s1_5->s1_16;
+    s1_6->s1_8->s1_16;
+    s1_9->s1_11->s1_13;
+    s1_16->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/optional-chaining--complex-3.js b/eslint/tests/fixtures/code-path-analysis/optional-chaining--complex-3.js
new file mode 100644 (file)
index 0000000..0afef1c
--- /dev/null
@@ -0,0 +1,41 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_5->s1_6->s1_7->s1_8->s1_10->s1_11->s1_12->s1_13->s1_14->s1_15->s1_16;
+s1_1->s1_10;
+s1_2->s1_4->s1_5->s1_10;
+s1_6->s1_8;
+s1_10->s1_16;
+s1_11->s1_13->s1_15;
+s1_16->final;
+*/
+
+(obj?.[cond ? k1 : k2]?.[k3 || k4])?.(a1 && a2, b1 ?? b2).foo(arg)
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nChainExpression:enter\nCallExpression:enter\nMemberExpression:enter\nCallExpression:enter\nChainExpression:enter\nMemberExpression:enter\nMemberExpression:enter\nIdentifier (obj)"];
+    s1_2[label="ConditionalExpression:enter\nIdentifier (cond)"];
+    s1_3[label="Identifier (k1)"];
+    s1_5[label="ConditionalExpression:exit\nMemberExpression:exit"];
+    s1_6[label="LogicalExpression:enter\nIdentifier (k3)"];
+    s1_7[label="Identifier (k4)"];
+    s1_8[label="LogicalExpression:exit\nMemberExpression:exit"];
+    s1_10[label="ChainExpression:exit"];
+    s1_11[label="LogicalExpression:enter\nIdentifier (a1)"];
+    s1_12[label="Identifier (a2)"];
+    s1_13[label="LogicalExpression:exit\nLogicalExpression:enter\nIdentifier (b1)"];
+    s1_14[label="Identifier (b2)"];
+    s1_15[label="LogicalExpression:exit\nCallExpression:exit\nIdentifier (foo)\nMemberExpression:exit\nIdentifier (arg)\nCallExpression:exit"];
+    s1_16[label="ChainExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    s1_4[label="Identifier (k2)"];
+    initial->s1_1->s1_2->s1_3->s1_5->s1_6->s1_7->s1_8->s1_10->s1_11->s1_12->s1_13->s1_14->s1_15->s1_16;
+    s1_1->s1_10;
+    s1_2->s1_4->s1_5->s1_10;
+    s1_6->s1_8;
+    s1_10->s1_16;
+    s1_11->s1_13->s1_15;
+    s1_16->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/optional-chaining--simple-1.js b/eslint/tests/fixtures/code-path-analysis/optional-chaining--simple-1.js
new file mode 100644 (file)
index 0000000..d466a4e
--- /dev/null
@@ -0,0 +1,19 @@
+/*expected
+initial->s1_1->s1_2->s1_3;
+s1_1->s1_3->final;
+*/
+
+obj?.aaa.bbb(arg)
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nChainExpression:enter\nCallExpression:enter\nMemberExpression:enter\nMemberExpression:enter\nIdentifier (obj)"];
+    s1_2[label="Identifier (aaa)\nMemberExpression:exit\nIdentifier (bbb)\nMemberExpression:exit\nIdentifier (arg)\nCallExpression:exit"];
+    s1_3[label="ChainExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3;
+    s1_1->s1_3->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/optional-chaining--simple-2.js b/eslint/tests/fixtures/code-path-analysis/optional-chaining--simple-2.js
new file mode 100644 (file)
index 0000000..a586752
--- /dev/null
@@ -0,0 +1,19 @@
+/*expected
+initial->s1_1->s1_2->s1_3;
+s1_1->s1_3->final;
+*/
+
+obj.foo?.(arg)
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nChainExpression:enter\nCallExpression:enter\nMemberExpression:enter\nIdentifier (obj)\nIdentifier (foo)\nMemberExpression:exit"];
+    s1_2[label="Identifier (arg)\nCallExpression:exit"];
+    s1_3[label="ChainExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3;
+    s1_1->s1_3->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/optional-chaining--simple-3.js b/eslint/tests/fixtures/code-path-analysis/optional-chaining--simple-3.js
new file mode 100644 (file)
index 0000000..fa43af9
--- /dev/null
@@ -0,0 +1,25 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_7;
+s1_1->s1_7;
+s1_2->s1_7;
+s1_3->s1_7->final;
+*/
+
+obj?.aaa?.bbb?.(arg)
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nChainExpression:enter\nCallExpression:enter\nMemberExpression:enter\nMemberExpression:enter\nIdentifier (obj)"];
+    s1_2[label="Identifier (aaa)\nMemberExpression:exit"];
+    s1_3[label="Identifier (bbb)\nMemberExpression:exit"];
+    s1_4[label="Identifier (arg)\nCallExpression:exit"];
+    s1_7[label="ChainExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4->s1_7;
+    s1_1->s1_7;
+    s1_2->s1_7;
+    s1_3->s1_7->final;
+}
+*/
diff --git a/eslint/tests/fixtures/code-path-analysis/optional-chaining--simple-4.js b/eslint/tests/fixtures/code-path-analysis/optional-chaining--simple-4.js
new file mode 100644 (file)
index 0000000..c101746
--- /dev/null
@@ -0,0 +1,19 @@
+/*expected
+initial->s1_1->s1_2->s1_3;
+s1_1->s1_3->final;
+*/
+
+func?.()(arg)
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program:enter\nExpressionStatement:enter\nChainExpression:enter\nCallExpression:enter\nCallExpression:enter\nIdentifier (func)\nCallExpression:exit"];
+    s1_2[label="Identifier (arg)\nCallExpression:exit"];
+    s1_3[label="ChainExpression:exit\nExpressionStatement:exit\nProgram:exit"];
+    initial->s1_1->s1_2->s1_3;
+    s1_1->s1_3->final;
+}
+*/
diff --git a/eslint/tests/fixtures/parsers/arrow-parens/generics-extends-complex.js b/eslint/tests/fixtures/parsers/arrow-parens/generics-extends-complex.js
new file mode 100644 (file)
index 0000000..4a27b49
--- /dev/null
@@ -0,0 +1,249 @@
+"use strict";
+
+/**
+ * Parser: @typescript-eslint/parser@3.5.0
+ * Source code:
+ * <T extends (A | B) & C>(a) => b
+ */
+
+exports.parse = () => ({
+    type: "Program",
+    body: [
+      {
+        type: "ExpressionStatement",
+        expression: {
+          type: "ArrowFunctionExpression",
+          generator: false,
+          id: null,
+          params: [
+            {
+              type: "Identifier",
+              name: "a",
+              range: [24, 25],
+              loc: {
+                start: { line: 1, column: 24 },
+                end: { line: 1, column: 25 },
+              },
+            },
+          ],
+          body: {
+            type: "Identifier",
+            name: "b",
+            range: [30, 31],
+            loc: { start: { line: 1, column: 30 }, end: { line: 1, column: 31 } },
+          },
+          async: false,
+          expression: true,
+          range: [0, 31],
+          loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 31 } },
+          typeParameters: {
+            type: "TSTypeParameterDeclaration",
+            range: [0, 23],
+            loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 23 } },
+            params: [
+              {
+                type: "TSTypeParameter",
+                name: {
+                  type: "Identifier",
+                  name: "T",
+                  range: [1, 2],
+                  loc: {
+                    start: { line: 1, column: 1 },
+                    end: { line: 1, column: 2 },
+                  },
+                },
+                constraint: {
+                  type: "TSIntersectionType",
+                  types: [
+                    {
+                      type: "TSParenthesizedType",
+                      typeAnnotation: {
+                        type: "TSUnionType",
+                        types: [
+                          {
+                            type: "TSTypeReference",
+                            typeName: {
+                              type: "Identifier",
+                              name: "A",
+                              range: [12, 13],
+                              loc: {
+                                start: { line: 1, column: 12 },
+                                end: { line: 1, column: 13 },
+                              },
+                            },
+                            range: [12, 13],
+                            loc: {
+                              start: { line: 1, column: 12 },
+                              end: { line: 1, column: 13 },
+                            },
+                          },
+                          {
+                            type: "TSTypeReference",
+                            typeName: {
+                              type: "Identifier",
+                              name: "B",
+                              range: [16, 17],
+                              loc: {
+                                start: { line: 1, column: 16 },
+                                end: { line: 1, column: 17 },
+                              },
+                            },
+                            range: [16, 17],
+                            loc: {
+                              start: { line: 1, column: 16 },
+                              end: { line: 1, column: 17 },
+                            },
+                          },
+                        ],
+                        range: [12, 17],
+                        loc: {
+                          start: { line: 1, column: 12 },
+                          end: { line: 1, column: 17 },
+                        },
+                      },
+                      range: [11, 18],
+                      loc: {
+                        start: { line: 1, column: 11 },
+                        end: { line: 1, column: 18 },
+                      },
+                    },
+                    {
+                      type: "TSTypeReference",
+                      typeName: {
+                        type: "Identifier",
+                        name: "C",
+                        range: [21, 22],
+                        loc: {
+                          start: { line: 1, column: 21 },
+                          end: { line: 1, column: 22 },
+                        },
+                      },
+                      range: [21, 22],
+                      loc: {
+                        start: { line: 1, column: 21 },
+                        end: { line: 1, column: 22 },
+                      },
+                    },
+                  ],
+                  range: [11, 22],
+                  loc: {
+                    start: { line: 1, column: 11 },
+                    end: { line: 1, column: 22 },
+                  },
+                },
+                range: [1, 22],
+                loc: {
+                  start: { line: 1, column: 1 },
+                  end: { line: 1, column: 22 },
+                },
+              },
+            ],
+          },
+        },
+        range: [0, 31],
+        loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 31 } },
+      },
+    ],
+    sourceType: "script",
+    range: [0, 31],
+    loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 31 } },
+    tokens: [
+      {
+        type: "Punctuator",
+        value: "<",
+        range: [0, 1],
+        loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } },
+      },
+      {
+        type: "Identifier",
+        value: "T",
+        range: [1, 2],
+        loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
+      },
+      {
+        type: "Keyword",
+        value: "extends",
+        range: [3, 10],
+        loc: { start: { line: 1, column: 3 }, end: { line: 1, column: 10 } },
+      },
+      {
+        type: "Punctuator",
+        value: "(",
+        range: [11, 12],
+        loc: { start: { line: 1, column: 11 }, end: { line: 1, column: 12 } },
+      },
+      {
+        type: "Identifier",
+        value: "A",
+        range: [12, 13],
+        loc: { start: { line: 1, column: 12 }, end: { line: 1, column: 13 } },
+      },
+      {
+        type: "Punctuator",
+        value: "|",
+        range: [14, 15],
+        loc: { start: { line: 1, column: 14 }, end: { line: 1, column: 15 } },
+      },
+      {
+        type: "Identifier",
+        value: "B",
+        range: [16, 17],
+        loc: { start: { line: 1, column: 16 }, end: { line: 1, column: 17 } },
+      },
+      {
+        type: "Punctuator",
+        value: ")",
+        range: [17, 18],
+        loc: { start: { line: 1, column: 17 }, end: { line: 1, column: 18 } },
+      },
+      {
+        type: "Punctuator",
+        value: "&",
+        range: [19, 20],
+        loc: { start: { line: 1, column: 19 }, end: { line: 1, column: 20 } },
+      },
+      {
+        type: "Identifier",
+        value: "C",
+        range: [21, 22],
+        loc: { start: { line: 1, column: 21 }, end: { line: 1, column: 22 } },
+      },
+      {
+        type: "Punctuator",
+        value: ">",
+        range: [22, 23],
+        loc: { start: { line: 1, column: 22 }, end: { line: 1, column: 23 } },
+      },
+      {
+        type: "Punctuator",
+        value: "(",
+        range: [23, 24],
+        loc: { start: { line: 1, column: 23 }, end: { line: 1, column: 24 } },
+      },
+      {
+        type: "Identifier",
+        value: "a",
+        range: [24, 25],
+        loc: { start: { line: 1, column: 24 }, end: { line: 1, column: 25 } },
+      },
+      {
+        type: "Punctuator",
+        value: ")",
+        range: [25, 26],
+        loc: { start: { line: 1, column: 25 }, end: { line: 1, column: 26 } },
+      },
+      {
+        type: "Punctuator",
+        value: "=>",
+        range: [27, 29],
+        loc: { start: { line: 1, column: 27 }, end: { line: 1, column: 29 } },
+      },
+      {
+        type: "Identifier",
+        value: "b",
+        range: [30, 31],
+        loc: { start: { line: 1, column: 30 }, end: { line: 1, column: 31 } },
+      },
+    ],
+    comments: [],
+  });
diff --git a/eslint/tests/fixtures/parsers/arrow-parens/generics-extends.js b/eslint/tests/fixtures/parsers/arrow-parens/generics-extends.js
new file mode 100644 (file)
index 0000000..ee70b56
--- /dev/null
@@ -0,0 +1,151 @@
+"use strict";
+
+/**
+ * Parser: @typescript-eslint/parser@3.5.0
+ * Source code:
+ * <T extends A>(a) => b
+ */
+
+exports.parse = () => ({
+    type: "Program",
+    body: [
+      {
+        type: "ExpressionStatement",
+        expression: {
+          type: "ArrowFunctionExpression",
+          generator: false,
+          id: null,
+          params: [
+            {
+              type: "Identifier",
+              name: "a",
+              range: [14, 15],
+              loc: {
+                start: { line: 1, column: 14 },
+                end: { line: 1, column: 15 },
+              },
+            },
+          ],
+          body: {
+            type: "Identifier",
+            name: "b",
+            range: [20, 21],
+            loc: { start: { line: 1, column: 20 }, end: { line: 1, column: 21 } },
+          },
+          async: false,
+          expression: true,
+          range: [0, 21],
+          loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 21 } },
+          typeParameters: {
+            type: "TSTypeParameterDeclaration",
+            range: [0, 13],
+            loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 13 } },
+            params: [
+              {
+                type: "TSTypeParameter",
+                name: {
+                  type: "Identifier",
+                  name: "T",
+                  range: [1, 2],
+                  loc: {
+                    start: { line: 1, column: 1 },
+                    end: { line: 1, column: 2 },
+                  },
+                },
+                constraint: {
+                  type: "TSTypeReference",
+                  typeName: {
+                    type: "Identifier",
+                    name: "A",
+                    range: [11, 12],
+                    loc: {
+                      start: { line: 1, column: 11 },
+                      end: { line: 1, column: 12 },
+                    },
+                  },
+                  range: [11, 12],
+                  loc: {
+                    start: { line: 1, column: 11 },
+                    end: { line: 1, column: 12 },
+                  },
+                },
+                range: [1, 12],
+                loc: {
+                  start: { line: 1, column: 1 },
+                  end: { line: 1, column: 12 },
+                },
+              },
+            ],
+          },
+        },
+        range: [0, 21],
+        loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 21 } },
+      },
+    ],
+    sourceType: "script",
+    range: [0, 21],
+    loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 21 } },
+    tokens: [
+      {
+        type: "Punctuator",
+        value: "<",
+        range: [0, 1],
+        loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } },
+      },
+      {
+        type: "Identifier",
+        value: "T",
+        range: [1, 2],
+        loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
+      },
+      {
+        type: "Keyword",
+        value: "extends",
+        range: [3, 10],
+        loc: { start: { line: 1, column: 3 }, end: { line: 1, column: 10 } },
+      },
+      {
+        type: "Identifier",
+        value: "A",
+        range: [11, 12],
+        loc: { start: { line: 1, column: 11 }, end: { line: 1, column: 12 } },
+      },
+      {
+        type: "Punctuator",
+        value: ">",
+        range: [12, 13],
+        loc: { start: { line: 1, column: 12 }, end: { line: 1, column: 13 } },
+      },
+      {
+        type: "Punctuator",
+        value: "(",
+        range: [13, 14],
+        loc: { start: { line: 1, column: 13 }, end: { line: 1, column: 14 } },
+      },
+      {
+        type: "Identifier",
+        value: "a",
+        range: [14, 15],
+        loc: { start: { line: 1, column: 14 }, end: { line: 1, column: 15 } },
+      },
+      {
+        type: "Punctuator",
+        value: ")",
+        range: [15, 16],
+        loc: { start: { line: 1, column: 15 }, end: { line: 1, column: 16 } },
+      },
+      {
+        type: "Punctuator",
+        value: "=>",
+        range: [17, 19],
+        loc: { start: { line: 1, column: 17 }, end: { line: 1, column: 19 } },
+      },
+      {
+        type: "Identifier",
+        value: "b",
+        range: [20, 21],
+        loc: { start: { line: 1, column: 20 }, end: { line: 1, column: 21 } },
+      },
+    ],
+    comments: [],
+  });
diff --git a/eslint/tests/fixtures/parsers/arrow-parens/generics-simple-async.js b/eslint/tests/fixtures/parsers/arrow-parens/generics-simple-async.js
new file mode 100644 (file)
index 0000000..6892612
--- /dev/null
@@ -0,0 +1,128 @@
+"use strict";
+
+/**
+ * Parser: @typescript-eslint/parser@3.5.0
+ * Source code:
+ * async <T>(a) => b
+ */
+
+exports.parse = () => ({
+    type: "Program",
+    body: [
+      {
+        type: "ExpressionStatement",
+        expression: {
+          type: "ArrowFunctionExpression",
+          generator: false,
+          id: null,
+          params: [
+            {
+              type: "Identifier",
+              name: "a",
+              range: [10, 11],
+              loc: {
+                start: { line: 1, column: 10 },
+                end: { line: 1, column: 11 },
+              },
+            },
+          ],
+          body: {
+            type: "Identifier",
+            name: "b",
+            range: [16, 17],
+            loc: { start: { line: 1, column: 16 }, end: { line: 1, column: 17 } },
+          },
+          async: true,
+          expression: true,
+          range: [0, 17],
+          loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 17 } },
+          typeParameters: {
+            type: "TSTypeParameterDeclaration",
+            range: [6, 9],
+            loc: { start: { line: 1, column: 6 }, end: { line: 1, column: 9 } },
+            params: [
+              {
+                type: "TSTypeParameter",
+                name: {
+                  type: "Identifier",
+                  name: "T",
+                  range: [7, 8],
+                  loc: {
+                    start: { line: 1, column: 7 },
+                    end: { line: 1, column: 8 },
+                  },
+                },
+                range: [7, 8],
+                loc: {
+                  start: { line: 1, column: 7 },
+                  end: { line: 1, column: 8 },
+                },
+              },
+            ],
+          },
+        },
+        range: [0, 17],
+        loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 17 } },
+      },
+    ],
+    sourceType: "script",
+    range: [0, 17],
+    loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 17 } },
+    tokens: [
+      {
+        type: "Identifier",
+        value: "async",
+        range: [0, 5],
+        loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 5 } },
+      },
+      {
+        type: "Punctuator",
+        value: "<",
+        range: [6, 7],
+        loc: { start: { line: 1, column: 6 }, end: { line: 1, column: 7 } },
+      },
+      {
+        type: "Identifier",
+        value: "T",
+        range: [7, 8],
+        loc: { start: { line: 1, column: 7 }, end: { line: 1, column: 8 } },
+      },
+      {
+        type: "Punctuator",
+        value: ">",
+        range: [8, 9],
+        loc: { start: { line: 1, column: 8 }, end: { line: 1, column: 9 } },
+      },
+      {
+        type: "Punctuator",
+        value: "(",
+        range: [9, 10],
+        loc: { start: { line: 1, column: 9 }, end: { line: 1, column: 10 } },
+      },
+      {
+        type: "Identifier",
+        value: "a",
+        range: [10, 11],
+        loc: { start: { line: 1, column: 10 }, end: { line: 1, column: 11 } },
+      },
+      {
+        type: "Punctuator",
+        value: ")",
+        range: [11, 12],
+        loc: { start: { line: 1, column: 11 }, end: { line: 1, column: 12 } },
+      },
+      {
+        type: "Punctuator",
+        value: "=>",
+        range: [13, 15],
+        loc: { start: { line: 1, column: 13 }, end: { line: 1, column: 15 } },
+      },
+      {
+        type: "Identifier",
+        value: "b",
+        range: [16, 17],
+        loc: { start: { line: 1, column: 16 }, end: { line: 1, column: 17 } },
+      },
+    ],
+    comments: [],
+  });
diff --git a/eslint/tests/fixtures/parsers/arrow-parens/generics-simple-no-params.js b/eslint/tests/fixtures/parsers/arrow-parens/generics-simple-no-params.js
new file mode 100644 (file)
index 0000000..ab17a7d
--- /dev/null
@@ -0,0 +1,106 @@
+"use strict";
+
+/**
+ * Parser: @typescript-eslint/parser@3.5.0
+ * Source code:
+ * <T>() => b
+ */
+
+exports.parse = () => ({
+    type: "Program",
+    body: [
+      {
+        type: "ExpressionStatement",
+        expression: {
+          type: "ArrowFunctionExpression",
+          generator: false,
+          id: null,
+          params: [],
+          body: {
+            type: "Identifier",
+            name: "b",
+            range: [9, 10],
+            loc: { start: { line: 1, column: 9 }, end: { line: 1, column: 10 } },
+          },
+          async: false,
+          expression: true,
+          range: [0, 10],
+          loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 10 } },
+          typeParameters: {
+            type: "TSTypeParameterDeclaration",
+            range: [0, 3],
+            loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 3 } },
+            params: [
+              {
+                type: "TSTypeParameter",
+                name: {
+                  type: "Identifier",
+                  name: "T",
+                  range: [1, 2],
+                  loc: {
+                    start: { line: 1, column: 1 },
+                    end: { line: 1, column: 2 },
+                  },
+                },
+                range: [1, 2],
+                loc: {
+                  start: { line: 1, column: 1 },
+                  end: { line: 1, column: 2 },
+                },
+              },
+            ],
+          },
+        },
+        range: [0, 10],
+        loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 10 } },
+      },
+    ],
+    sourceType: "script",
+    range: [0, 10],
+    loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 10 } },
+    tokens: [
+      {
+        type: "Punctuator",
+        value: "<",
+        range: [0, 1],
+        loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } },
+      },
+      {
+        type: "Identifier",
+        value: "T",
+        range: [1, 2],
+        loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
+      },
+      {
+        type: "Punctuator",
+        value: ">",
+        range: [2, 3],
+        loc: { start: { line: 1, column: 2 }, end: { line: 1, column: 3 } },
+      },
+      {
+        type: "Punctuator",
+        value: "(",
+        range: [3, 4],
+        loc: { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } },
+      },
+      {
+        type: "Punctuator",
+        value: ")",
+        range: [4, 5],
+        loc: { start: { line: 1, column: 4 }, end: { line: 1, column: 5 } },
+      },
+      {
+        type: "Punctuator",
+        value: "=>",
+        range: [6, 8],
+        loc: { start: { line: 1, column: 6 }, end: { line: 1, column: 8 } },
+      },
+      {
+        type: "Identifier",
+        value: "b",
+        range: [9, 10],
+        loc: { start: { line: 1, column: 9 }, end: { line: 1, column: 10 } },
+      },
+    ],
+    comments: [],
+  });
diff --git a/eslint/tests/fixtures/parsers/arrow-parens/generics-simple.js b/eslint/tests/fixtures/parsers/arrow-parens/generics-simple.js
new file mode 100644 (file)
index 0000000..92f1a5b
--- /dev/null
@@ -0,0 +1,119 @@
+"use strict";
+
+/**
+ * Parser: @typescript-eslint/parser@3.5.0
+ * Source code:
+ * <T>(a) => b
+ */
+
+exports.parse = () => ({
+    type: "Program",
+    body: [
+      {
+        type: "ExpressionStatement",
+        expression: {
+          type: "ArrowFunctionExpression",
+          generator: false,
+          id: null,
+          params: [
+            {
+              type: "Identifier",
+              name: "a",
+              range: [4, 5],
+              loc: { start: { line: 1, column: 4 }, end: { line: 1, column: 5 } },
+            },
+          ],
+          body: {
+            type: "Identifier",
+            name: "b",
+            range: [10, 11],
+            loc: { start: { line: 1, column: 10 }, end: { line: 1, column: 11 } },
+          },
+          async: false,
+          expression: true,
+          range: [0, 11],
+          loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 11 } },
+          typeParameters: {
+            type: "TSTypeParameterDeclaration",
+            range: [0, 3],
+            loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 3 } },
+            params: [
+              {
+                type: "TSTypeParameter",
+                name: {
+                  type: "Identifier",
+                  name: "T",
+                  range: [1, 2],
+                  loc: {
+                    start: { line: 1, column: 1 },
+                    end: { line: 1, column: 2 },
+                  },
+                },
+                range: [1, 2],
+                loc: {
+                  start: { line: 1, column: 1 },
+                  end: { line: 1, column: 2 },
+                },
+              },
+            ],
+          },
+        },
+        range: [0, 11],
+        loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 11 } },
+      },
+    ],
+    sourceType: "script",
+    range: [0, 11],
+    loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 11 } },
+    tokens: [
+      {
+        type: "Punctuator",
+        value: "<",
+        range: [0, 1],
+        loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } },
+      },
+      {
+        type: "Identifier",
+        value: "T",
+        range: [1, 2],
+        loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 2 } },
+      },
+      {
+        type: "Punctuator",
+        value: ">",
+        range: [2, 3],
+        loc: { start: { line: 1, column: 2 }, end: { line: 1, column: 3 } },
+      },
+      {
+        type: "Punctuator",
+        value: "(",
+        range: [3, 4],
+        loc: { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } },
+      },
+      {
+        type: "Identifier",
+        value: "a",
+        range: [4, 5],
+        loc: { start: { line: 1, column: 4 }, end: { line: 1, column: 5 } },
+      },
+      {
+        type: "Punctuator",
+        value: ")",
+        range: [5, 6],
+        loc: { start: { line: 1, column: 5 }, end: { line: 1, column: 6 } },
+      },
+      {
+        type: "Punctuator",
+        value: "=>",
+        range: [7, 9],
+        loc: { start: { line: 1, column: 7 }, end: { line: 1, column: 9 } },
+      },
+      {
+        type: "Identifier",
+        value: "b",
+        range: [10, 11],
+        loc: { start: { line: 1, column: 10 }, end: { line: 1, column: 11 } },
+      },
+    ],
+    comments: [],
+  });
diff --git a/eslint/tests/fixtures/parsers/space-before-blocks/return-type-keyword-1.js b/eslint/tests/fixtures/parsers/space-before-blocks/return-type-keyword-1.js
new file mode 100644 (file)
index 0000000..295eb65
--- /dev/null
@@ -0,0 +1,206 @@
+"use strict";
+
+/**
+ * Parser: @typescript-eslint/parser@4.2.0
+ * Source code:
+ * class A { foo(bar: string): void{} }
+ */
+
+exports.parse = () => ({
+    type: "Program",
+    body: [
+      {
+        type: "ClassDeclaration",
+        id: {
+          type: "Identifier",
+          name: "A",
+          range: [6, 7],
+          loc: { start: { line: 1, column: 6 }, end: { line: 1, column: 7 } },
+        },
+        body: {
+          type: "ClassBody",
+          body: [
+            {
+              type: "MethodDefinition",
+              key: {
+                type: "Identifier",
+                name: "foo",
+                range: [10, 13],
+                loc: {
+                  start: { line: 1, column: 10 },
+                  end: { line: 1, column: 13 },
+                },
+              },
+              value: {
+                type: "FunctionExpression",
+                id: null,
+                generator: false,
+                expression: false,
+                async: false,
+                body: {
+                  type: "BlockStatement",
+                  body: [],
+                  range: [32, 34],
+                  loc: {
+                    start: { line: 1, column: 32 },
+                    end: { line: 1, column: 34 },
+                  },
+                },
+                range: [13, 34],
+                params: [
+                  {
+                    type: "Identifier",
+                    name: "bar",
+                    range: [14, 25],
+                    loc: {
+                      start: { line: 1, column: 14 },
+                      end: { line: 1, column: 25 },
+                    },
+                    typeAnnotation: {
+                      type: "TSTypeAnnotation",
+                      loc: {
+                        start: { line: 1, column: 17 },
+                        end: { line: 1, column: 25 },
+                      },
+                      range: [17, 25],
+                      typeAnnotation: {
+                        type: "TSStringKeyword",
+                        range: [19, 25],
+                        loc: {
+                          start: { line: 1, column: 19 },
+                          end: { line: 1, column: 25 },
+                        },
+                      },
+                    },
+                  },
+                ],
+                loc: {
+                  start: { line: 1, column: 13 },
+                  end: { line: 1, column: 34 },
+                },
+                returnType: {
+                  type: "TSTypeAnnotation",
+                  loc: {
+                    start: { line: 1, column: 26 },
+                    end: { line: 1, column: 32 },
+                  },
+                  range: [26, 32],
+                  typeAnnotation: {
+                    type: "TSVoidKeyword",
+                    range: [28, 32],
+                    loc: {
+                      start: { line: 1, column: 28 },
+                      end: { line: 1, column: 32 },
+                    },
+                  },
+                },
+              },
+              computed: false,
+              static: false,
+              kind: "method",
+              range: [10, 34],
+              loc: {
+                start: { line: 1, column: 10 },
+                end: { line: 1, column: 34 },
+              },
+            },
+          ],
+          range: [8, 36],
+          loc: { start: { line: 1, column: 8 }, end: { line: 1, column: 36 } },
+        },
+        superClass: null,
+        range: [0, 36],
+        loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 36 } },
+      },
+    ],
+    sourceType: "script",
+    range: [0, 36],
+    loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 36 } },
+    tokens: [
+      {
+        type: "Keyword",
+        value: "class",
+        range: [0, 5],
+        loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 5 } },
+      },
+      {
+        type: "Identifier",
+        value: "A",
+        range: [6, 7],
+        loc: { start: { line: 1, column: 6 }, end: { line: 1, column: 7 } },
+      },
+      {
+        type: "Punctuator",
+        value: "{",
+        range: [8, 9],
+        loc: { start: { line: 1, column: 8 }, end: { line: 1, column: 9 } },
+      },
+      {
+        type: "Identifier",
+        value: "foo",
+        range: [10, 13],
+        loc: { start: { line: 1, column: 10 }, end: { line: 1, column: 13 } },
+      },
+      {
+        type: "Punctuator",
+        value: "(",
+        range: [13, 14],
+        loc: { start: { line: 1, column: 13 }, end: { line: 1, column: 14 } },
+      },
+      {
+        type: "Identifier",
+        value: "bar",
+        range: [14, 17],
+        loc: { start: { line: 1, column: 14 }, end: { line: 1, column: 17 } },
+      },
+      {
+        type: "Punctuator",
+        value: ":",
+        range: [17, 18],
+        loc: { start: { line: 1, column: 17 }, end: { line: 1, column: 18 } },
+      },
+      {
+        type: "Identifier",
+        value: "string",
+        range: [19, 25],
+        loc: { start: { line: 1, column: 19 }, end: { line: 1, column: 25 } },
+      },
+      {
+        type: "Punctuator",
+        value: ")",
+        range: [25, 26],
+        loc: { start: { line: 1, column: 25 }, end: { line: 1, column: 26 } },
+      },
+      {
+        type: "Punctuator",
+        value: ":",
+        range: [26, 27],
+        loc: { start: { line: 1, column: 26 }, end: { line: 1, column: 27 } },
+      },
+      {
+        type: "Keyword",
+        value: "void",
+        range: [28, 32],
+        loc: { start: { line: 1, column: 28 }, end: { line: 1, column: 32 } },
+      },
+      {
+        type: "Punctuator",
+        value: "{",
+        range: [32, 33],
+        loc: { start: { line: 1, column: 32 }, end: { line: 1, column: 33 } },
+      },
+      {
+        type: "Punctuator",
+        value: "}",
+        range: [33, 34],
+        loc: { start: { line: 1, column: 33 }, end: { line: 1, column: 34 } },
+      },
+      {
+        type: "Punctuator",
+        value: "}",
+        range: [35, 36],
+        loc: { start: { line: 1, column: 35 }, end: { line: 1, column: 36 } },
+      },
+    ],
+    comments: [],
+  });
diff --git a/eslint/tests/fixtures/parsers/space-before-blocks/return-type-keyword-2.js b/eslint/tests/fixtures/parsers/space-before-blocks/return-type-keyword-2.js
new file mode 100644 (file)
index 0000000..48c444b
--- /dev/null
@@ -0,0 +1,98 @@
+"use strict";
+
+/**
+ * Parser: @typescript-eslint/parser@4.2.0
+ * Source code:
+ * function foo(): null {}
+ */
+
+exports.parse = () => ({
+    type: "Program",
+    body: [
+      {
+        type: "FunctionDeclaration",
+        id: {
+          type: "Identifier",
+          name: "foo",
+          range: [9, 12],
+          loc: { start: { line: 1, column: 9 }, end: { line: 1, column: 12 } },
+        },
+        generator: false,
+        expression: false,
+        async: false,
+        params: [],
+        body: {
+          type: "BlockStatement",
+          body: [],
+          range: [21, 23],
+          loc: { start: { line: 1, column: 21 }, end: { line: 1, column: 23 } },
+        },
+        range: [0, 23],
+        loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 23 } },
+        returnType: {
+          type: "TSTypeAnnotation",
+          loc: { start: { line: 1, column: 14 }, end: { line: 1, column: 20 } },
+          range: [14, 20],
+          typeAnnotation: {
+            type: "TSNullKeyword",
+            range: [16, 20],
+            loc: { start: { line: 1, column: 16 }, end: { line: 1, column: 20 } },
+          },
+        },
+      },
+    ],
+    sourceType: "script",
+    range: [0, 23],
+    loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 23 } },
+    tokens: [
+      {
+        type: "Keyword",
+        value: "function",
+        range: [0, 8],
+        loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 8 } },
+      },
+      {
+        type: "Identifier",
+        value: "foo",
+        range: [9, 12],
+        loc: { start: { line: 1, column: 9 }, end: { line: 1, column: 12 } },
+      },
+      {
+        type: "Punctuator",
+        value: "(",
+        range: [12, 13],
+        loc: { start: { line: 1, column: 12 }, end: { line: 1, column: 13 } },
+      },
+      {
+        type: "Punctuator",
+        value: ")",
+        range: [13, 14],
+        loc: { start: { line: 1, column: 13 }, end: { line: 1, column: 14 } },
+      },
+      {
+        type: "Punctuator",
+        value: ":",
+        range: [14, 15],
+        loc: { start: { line: 1, column: 14 }, end: { line: 1, column: 15 } },
+      },
+      {
+        type: "Keyword",
+        value: "null",
+        range: [16, 20],
+        loc: { start: { line: 1, column: 16 }, end: { line: 1, column: 20 } },
+      },
+      {
+        type: "Punctuator",
+        value: "{",
+        range: [21, 22],
+        loc: { start: { line: 1, column: 21 }, end: { line: 1, column: 22 } },
+      },
+      {
+        type: "Punctuator",
+        value: "}",
+        range: [22, 23],
+        loc: { start: { line: 1, column: 22 }, end: { line: 1, column: 23 } },
+      },
+    ],
+    comments: [],
+  });
index 382885613d258eb0dc4a5541fc3fc06a8de2a050..5841f15bfa13d4421cf68532663a772408b3a63a 100644 (file)
@@ -7,27 +7,31 @@
 // Rule Definition
 //------------------------------------------------------------------------------
 
-module.exports = function(context) {
-
-    "use strict";
-
-
-    var sourceCode = context.getSourceCode();
-
-    return {
-
-        "VariableDeclaration": function(node) {
-            if (node.kind === "var") {
-                context.report({
-                    node: node,
-                    loc: sourceCode.getFirstToken(node).loc,
-                    message: "Bad var.",
-                    fix: function(fixer) {
-                        return fixer.remove(sourceCode.getFirstToken(node));
-                    }
-                })
+"use strict";
+
+module.exports = {
+
+    meta: {
+        fixable: "code"
+    },
+
+    create(context) {
+
+        var sourceCode = context.getSourceCode();
+
+        return {
+            "VariableDeclaration": function(node) {
+                if (node.kind === "var") {
+                    context.report({
+                        node: node,
+                        loc: sourceCode.getFirstToken(node).loc,
+                        message: "Bad var.",
+                        fix: function(fixer) {
+                            return fixer.remove(sourceCode.getFirstToken(node));
+                        }
+                    })
+                }
             }
-        }
-    };
-
+        };
+    }
 };
diff --git a/eslint/tests/lib/cli-engine/cascading-config-array-factory.js b/eslint/tests/lib/cli-engine/cascading-config-array-factory.js
deleted file mode 100644 (file)
index 8471ac3..0000000
+++ /dev/null
@@ -1,1601 +0,0 @@
-/**
- * @fileoverview Tests for CascadingConfigArrayFactory class.
- * @author Toru Nagashima <https://github.com/mysticatea>
- */
-"use strict";
-
-const fs = require("fs");
-const path = require("path");
-const os = require("os");
-const { assert } = require("chai");
-const sh = require("shelljs");
-const sinon = require("sinon");
-const { ConfigArrayFactory } = require("../../../lib/cli-engine/config-array-factory");
-const { ExtractedConfig } = require("../../../lib/cli-engine/config-array/extracted-config");
-const { defineCascadingConfigArrayFactoryWithInMemoryFileSystem } = require("../../_utils");
-
-/** @typedef {InstanceType<ReturnType<defineCascadingConfigArrayFactoryWithInMemoryFileSystem>["CascadingConfigArrayFactory"]>} CascadingConfigArrayFactory */
-/** @typedef {ReturnType<CascadingConfigArrayFactory["getConfigArrayForFile"]>} ConfigArray */
-
-const cwdIgnorePatterns = new ConfigArrayFactory()
-    .loadDefaultESLintIgnore()[0]
-    .ignorePattern
-    .patterns;
-
-describe("CascadingConfigArrayFactory", () => {
-    describe("'getConfigArrayForFile(filePath)' method should retrieve the proper configuration.", () => {
-        describe("with three directories ('lib', 'lib/nested', 'test') that contains 'one.js' and 'two.js'", () => {
-            const root = path.join(os.tmpdir(), "eslint/cli-engine/cascading-config-array-factory");
-            const files = {
-                /* eslint-disable quote-props */
-                "lib": {
-                    "nested": {
-                        "one.js": "",
-                        "two.js": "",
-                        "parser.js": "",
-                        ".eslintrc.yml": "parser: './parser'"
-                    },
-                    "one.js": "",
-                    "two.js": ""
-                },
-                "test": {
-                    "one.js": "",
-                    "two.js": "",
-                    ".eslintrc.yml": "env: { mocha: true }"
-                },
-                ".eslintignore": "/lib/nested/parser.js",
-                ".eslintrc.json": JSON.stringify({
-                    rules: {
-                        "no-undef": "error",
-                        "no-unused-vars": "error"
-                    }
-                })
-                /* eslint-enable quote-props */
-            };
-            const { CascadingConfigArrayFactory } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({ cwd: () => root, files });
-
-            /** @type {CascadingConfigArrayFactory} */
-            let factory;
-
-            beforeEach(() => {
-                factory = new CascadingConfigArrayFactory();
-            });
-
-            it("should retrieve the config '.eslintrc.json' if 'lib/one.js' was given.", () => {
-                const config = factory.getConfigArrayForFile("lib/one.js");
-
-                assert.strictEqual(config.length, 3);
-                assert.strictEqual(config[0].name, "DefaultIgnorePattern");
-                assert.strictEqual(config[1].filePath, path.join(root, ".eslintrc.json"));
-                assert.strictEqual(config[2].filePath, path.join(root, ".eslintignore"));
-            });
-
-            it("should retrieve the merged config of '.eslintrc.json' and 'lib/nested/.eslintrc.yml' if 'lib/nested/one.js' was given.", () => {
-                const config = factory.getConfigArrayForFile("lib/nested/one.js");
-
-                assert.strictEqual(config.length, 4);
-                assert.strictEqual(config[0].name, "DefaultIgnorePattern");
-                assert.strictEqual(config[1].filePath, path.join(root, ".eslintrc.json"));
-                assert.strictEqual(config[2].filePath, path.join(root, "lib/nested/.eslintrc.yml"));
-                assert.strictEqual(config[3].filePath, path.join(root, ".eslintignore"));
-            });
-
-            it("should retrieve the config '.eslintrc.json' if 'lib/non-exist.js' was given.", () => {
-                const config = factory.getConfigArrayForFile("lib/non-exist.js");
-
-                assert.strictEqual(config.length, 3);
-                assert.strictEqual(config[0].name, "DefaultIgnorePattern");
-                assert.strictEqual(config[1].filePath, path.join(root, ".eslintrc.json"));
-                assert.strictEqual(config[2].filePath, path.join(root, ".eslintignore"));
-            });
-        });
-
-        describe("deprecation warnings", () => {
-            let uid = 0;
-            let uniqueHomeDirName = "";
-            let homeDir = "";
-            let cwd = "";
-
-            /** @type {{code:string, message:string}[]} */
-            let warnings = [];
-
-            /** @type {CascadingConfigArrayFactory} */
-            let factory = null;
-
-            /** @type {ConfigArray} */
-            let config = null;
-
-            /**
-             * Store a reported warning object if that code starts with `ESLINT_`.
-             * @param {{code:string, message:string}} w The warning object to store.
-             * @returns {void}
-             */
-            function onWarning(w) {
-                if (w.code.startsWith("ESLINT_")) {
-                    warnings.push({ code: w.code, message: w.message });
-                }
-            }
-
-            /**
-             * Delay to wait for 'warning' events.
-             * @returns {Promise<void>} The promise that will be fulfilled after wait a timer.
-             */
-            function delay() {
-                return new Promise(resolve => setTimeout(resolve, 0));
-            }
-
-            beforeEach(() => {
-                uniqueHomeDirName = `home_${++uid}`;
-                homeDir = path.join(__dirname, `../../../${uniqueHomeDirName}`);
-                warnings = [];
-                sinon.stub(os, "homedir").returns(homeDir);
-                process.on("warning", onWarning);
-            });
-            afterEach(() => {
-                os.homedir.restore();
-                process.removeListener("warning", onWarning);
-            });
-
-            describe("when '~/.eslintrc.json' exists and CWD is `~/`", () => {
-                beforeEach(() => {
-                    cwd = homeDir;
-                    const { CascadingConfigArrayFactory } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
-                        cwd: () => cwd,
-                        files: {
-
-                            // ~/.eslintrc.json
-                            ".eslintrc.json": JSON.stringify({ rules: { eqeqeq: "error" } }),
-
-                            // other files
-                            "exist-with-root/test.js": "",
-                            "exist-with-root/.eslintrc.json": JSON.stringify({ root: true, rules: { yoda: "error" } }),
-                            "exist/test.js": "",
-                            "exist/.eslintrc.json": JSON.stringify({ rules: { yoda: "error" } }),
-                            "not-exist/test.js": ""
-                        }
-                    });
-
-                    factory = new CascadingConfigArrayFactory({ cwd });
-                });
-
-                // no warning.
-                describe("when it lints 'subdir/exist-with-root/test.js'", () => {
-                    beforeEach(async () => {
-                        config = factory.getConfigArrayForFile("exist-with-root/test.js");
-                        await delay();
-                    });
-
-                    it("should not raise any warnings.", () => {
-                        assert.deepStrictEqual(warnings, []);
-                    });
-
-                    it("should not load '~/.eslintrc.json'.", () => {
-                        assert.deepStrictEqual(
-                            config.extractConfig("a.js").rules,
-                            { yoda: ["error"] }
-                        );
-                    });
-                });
-
-                // no warning.
-                describe("when it lints 'subdir/exist/test.js'", () => {
-                    beforeEach(async () => {
-                        config = factory.getConfigArrayForFile("exist/test.js");
-                        await delay();
-                    });
-
-                    it("should not raise any warnings.", () => {
-                        assert.deepStrictEqual(warnings, []);
-                    });
-
-                    it("should load '~/.eslintrc.json'.", () => {
-                        assert.deepStrictEqual(
-                            config.extractConfig("a.js").rules,
-                            { eqeqeq: ["error"], yoda: ["error"] }
-                        );
-                    });
-                });
-
-                // no warning
-                describe("when it lints 'subdir/not-exist/test.js'", () => {
-                    beforeEach(async () => {
-                        config = factory.getConfigArrayForFile("not-exist/test.js");
-                        await delay();
-                    });
-
-                    it("should not raise any warnings.", () => {
-                        assert.deepStrictEqual(warnings, []);
-                    });
-
-                    it("should load '~/.eslintrc.json'.", () => {
-                        assert.deepStrictEqual(
-                            config.extractConfig("a.js").rules,
-                            { eqeqeq: ["error"] }
-                        );
-                    });
-                });
-            });
-
-            describe("when '~/.eslintrc.json' exists and CWD is `~/subdir`", () => {
-                beforeEach(() => {
-                    cwd = path.join(homeDir, "subdir");
-                    const { CascadingConfigArrayFactory } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
-                        cwd: () => cwd,
-                        files: {
-
-                            // ~/.eslintrc.json
-                            "../.eslintrc.json": JSON.stringify({ rules: { eqeqeq: "error" } }),
-
-                            // other files
-                            "exist-with-root/test.js": "",
-                            "exist-with-root/.eslintrc.json": JSON.stringify({ root: true, rules: { yoda: "error" } }),
-                            "exist/test.js": "",
-                            "exist/.eslintrc.json": JSON.stringify({ rules: { yoda: "error" } }),
-                            "not-exist/test.js": ""
-                        }
-                    });
-
-                    factory = new CascadingConfigArrayFactory({ cwd });
-                });
-
-                // Project's config file has `root:true`, then no warning.
-                describe("when it lints 'subdir/exist-with-root/test.js'", () => {
-                    beforeEach(async () => {
-                        config = factory.getConfigArrayForFile("exist-with-root/test.js");
-                        await delay();
-                    });
-
-                    it("should not raise any warnings.", () => {
-                        assert.deepStrictEqual(warnings, []);
-                    });
-
-                    it("should not load '~/.eslintrc.json'.", () => {
-                        assert.deepStrictEqual(
-                            config.extractConfig("a.js").rules,
-                            { yoda: ["error"] }
-                        );
-                    });
-                });
-
-                // Project's config file doesn't have `root:true` and home is ancestor, then ESLINT_PERSONAL_CONFIG_SUPPRESS.
-                describe("when it lints 'subdir/exist/test.js'", () => {
-                    beforeEach(async () => {
-                        config = factory.getConfigArrayForFile("exist/test.js");
-                        await delay();
-                    });
-
-                    it("should raise an ESLINT_PERSONAL_CONFIG_SUPPRESS warning.", () => {
-                        assert.deepStrictEqual(warnings, [
-                            {
-                                code: "ESLINT_PERSONAL_CONFIG_SUPPRESS",
-                                message: `'~/.eslintrc.*' config files have been deprecated. Please remove it or add 'root:true' to the config files in your projects in order to avoid loading '~/.eslintrc.*' accidentally. (found in "${uniqueHomeDirName}${path.sep}.eslintrc.json")`
-                            }
-                        ]);
-                    });
-
-                    it("should not load '~/.eslintrc.json'.", () => {
-                        assert.deepStrictEqual(
-                            config.extractConfig("a.js").rules,
-                            { yoda: ["error"] }
-                        );
-                    });
-                });
-
-                /*
-                 * Project's config file doesn't exist and home is ancestor, then no warning.
-                 * In this case, ESLint will continue to use `~/.eslintrc.json` even if personal config file feature is removed.
-                 */
-                describe("when it lints 'subdir/not-exist/test.js'", () => {
-                    beforeEach(async () => {
-                        config = factory.getConfigArrayForFile("not-exist/test.js");
-                        await delay();
-                    });
-
-                    it("should not raise any warnings.", () => {
-                        assert.deepStrictEqual(warnings, []);
-                    });
-
-                    it("should load '~/.eslintrc.json'.", () => {
-                        assert.deepStrictEqual(
-                            config.extractConfig("a.js").rules,
-                            { eqeqeq: ["error"] }
-                        );
-                    });
-                });
-            });
-
-            describe("when '~/.eslintrc.json' exists and CWD is `~/../another`", () => {
-                beforeEach(() => {
-                    cwd = path.join(homeDir, "../another");
-                    const { CascadingConfigArrayFactory } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
-                        cwd: () => cwd,
-                        files: {
-
-                            // ~/.eslintrc.json
-                            [`../${uniqueHomeDirName}/.eslintrc.json`]: JSON.stringify({ rules: { eqeqeq: "error" } }),
-
-                            // other files
-                            "exist-with-root/test.js": "",
-                            "exist-with-root/.eslintrc.json": JSON.stringify({ root: true, rules: { yoda: "error" } }),
-                            "exist/test.js": "",
-                            "exist/.eslintrc.json": JSON.stringify({ rules: { yoda: "error" } }),
-                            "not-exist/test.js": ""
-                        }
-                    });
-
-                    factory = new CascadingConfigArrayFactory({ cwd });
-                });
-
-                // Project's config file has `root:true`, then no warning.
-                describe("when it lints 'exist-with-root/test.js'", () => {
-                    beforeEach(async () => {
-                        config = factory.getConfigArrayForFile("exist-with-root/test.js");
-                        await delay();
-                    });
-
-                    it("should not raise any warnings.", () => {
-                        assert.deepStrictEqual(warnings, []);
-                    });
-
-                    it("should not load '~/.eslintrc.json'.", () => {
-                        assert.deepStrictEqual(
-                            config.extractConfig("a.js").rules,
-                            { yoda: ["error"] }
-                        );
-                    });
-                });
-
-                // Project's config file doesn't have `root:true` but home is not ancestor, then no warning.
-                describe("when it lints 'exist/test.js'", () => {
-                    beforeEach(async () => {
-                        config = factory.getConfigArrayForFile("exist/test.js");
-                        await delay();
-                    });
-
-                    it("should not raise any warnings.", () => {
-                        assert.deepStrictEqual(warnings, []);
-                    });
-
-                    it("should not load '~/.eslintrc.json'.", () => {
-                        assert.deepStrictEqual(
-                            config.extractConfig("a.js").rules,
-                            { yoda: ["error"] }
-                        );
-                    });
-                });
-
-                // Project's config file doesn't exist and home is not ancestor, then ESLINT_PERSONAL_CONFIG_LOAD.
-                describe("when it lints 'not-exist/test.js'", () => {
-                    beforeEach(async () => {
-                        config = factory.getConfigArrayForFile("not-exist/test.js");
-                        await delay();
-                    });
-
-                    it("should raise an ESLINT_PERSONAL_CONFIG_LOAD warning.", () => {
-                        assert.deepStrictEqual(warnings, [
-                            {
-                                code: "ESLINT_PERSONAL_CONFIG_LOAD",
-                                message: `'~/.eslintrc.*' config files have been deprecated. Please use a config file per project or the '--config' option. (found in "${uniqueHomeDirName}${path.sep}.eslintrc.json")`
-                            }
-                        ]);
-                    });
-
-                    it("should load '~/.eslintrc.json'.", () => {
-                        assert.deepStrictEqual(
-                            config.extractConfig("a.js").rules,
-                            { eqeqeq: ["error"] }
-                        );
-                    });
-                });
-            });
-
-            describe("when '~/.eslintrc.json' doesn't exist and CWD is `~/subdir`", () => {
-                beforeEach(() => {
-                    cwd = path.join(homeDir, "subdir");
-                    const { CascadingConfigArrayFactory } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
-                        cwd: () => cwd,
-                        files: {
-                            "exist-with-root/test.js": "",
-                            "exist-with-root/.eslintrc.json": JSON.stringify({ root: true, rules: { yoda: "error" } }),
-                            "exist/test.js": "",
-                            "exist/.eslintrc.json": JSON.stringify({ rules: { yoda: "error" } }),
-                            "not-exist/test.js": ""
-                        }
-                    });
-
-                    factory = new CascadingConfigArrayFactory({ cwd });
-                });
-
-                describe("when it lints 'subdir/exist/test.js'", () => {
-                    beforeEach(async () => {
-                        config = factory.getConfigArrayForFile("exist/test.js");
-                        await delay();
-                    });
-
-                    it("should not raise any warnings.", () => {
-                        assert.deepStrictEqual(warnings, []);
-                    });
-                });
-            });
-
-            describe("when '~/.eslintrc.json' doesn't exist and CWD is `~/../another`", () => {
-                beforeEach(() => {
-                    cwd = path.join(homeDir, "../another");
-                    const { CascadingConfigArrayFactory } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
-                        cwd: () => cwd,
-                        files: {
-                            "exist-with-root/test.js": "",
-                            "exist-with-root/.eslintrc.json": JSON.stringify({ root: true, rules: { yoda: "error" } }),
-                            "exist/test.js": "",
-                            "exist/.eslintrc.json": JSON.stringify({ rules: { yoda: "error" } }),
-                            "not-exist/test.js": ""
-                        }
-                    });
-
-                    factory = new CascadingConfigArrayFactory({ cwd });
-                });
-
-                describe("when it lints 'not-exist/test.js'", () => {
-                    beforeEach(async () => {
-                        config = factory.getConfigArrayForFile("not-exist/test.js", { ignoreNotFoundError: true });
-                        await delay();
-                    });
-
-                    it("should not raise any warnings.", () => {
-                        assert.deepStrictEqual(warnings, []);
-                    });
-                });
-            });
-        });
-
-        // This group moved from 'tests/lib/config.js' when refactoring to keep the cumulated test cases.
-        describe("with 'tests/fixtures/config-hierarchy' files", () => {
-            const { CascadingConfigArrayFactory } = require("../../../lib/cli-engine/cascading-config-array-factory");
-            let fixtureDir;
-
-            const DIRECTORY_CONFIG_HIERARCHY = require("../../fixtures/config-hierarchy/file-structure.json");
-
-            /**
-             * Returns the path inside of the fixture directory.
-             * @param {...string} args file path segments.
-             * @returns {string} The path inside the fixture directory.
-             * @private
-             */
-            function getFixturePath(...args) {
-                return path.join(fixtureDir, "config-hierarchy", ...args);
-            }
-
-            /**
-             * Mocks the current user's home path
-             * @param {string} fakeUserHomePath fake user's home path
-             * @returns {void}
-             * @private
-             */
-            function mockOsHomedir(fakeUserHomePath) {
-                sinon.stub(os, "homedir")
-                    .returns(fakeUserHomePath);
-            }
-
-            /**
-             * Assert that given two objects have the same properties with the
-             * same value for each.
-             *
-             * The `expected` object is merged with the default values of config
-             * data before comparing, so you can specify only the properties you
-             * focus on.
-             * @param {Object} actual The config object to check.
-             * @param {Object} expected What the config object should look like.
-             * @returns {void}
-             * @private
-             */
-            function assertConfigsEqual(actual, expected) {
-                const defaults = new ExtractedConfig().toCompatibleObjectAsConfigFileContent();
-
-                assert.deepStrictEqual(actual, { ...defaults, ...expected });
-            }
-
-            /**
-             * Wait for the next tick.
-             * @returns {Promise<void>} -
-             */
-            function nextTick() {
-                return new Promise(resolve => process.nextTick(resolve));
-            }
-
-            /**
-             * Get the config data for a file.
-             * @param {CascadingConfigArrayFactory} factory The factory to get config.
-             * @param {string} filePath The path to a source code.
-             * @returns {Object} The gotten config.
-             */
-            function getConfig(factory, filePath = "a.js") {
-                const { cwd } = factory;
-                const absolutePath = path.resolve(cwd, filePath);
-
-                return factory
-                    .getConfigArrayForFile(absolutePath)
-                    .extractConfig(absolutePath)
-                    .toCompatibleObjectAsConfigFileContent();
-            }
-
-            // copy into clean area so as not to get "infected" by this project's .eslintrc files
-            before(() => {
-                fixtureDir = `${os.tmpdir()}/eslint/fixtures`;
-                sh.mkdir("-p", fixtureDir);
-                sh.cp("-r", "./tests/fixtures/config-hierarchy", fixtureDir);
-                sh.cp("-r", "./tests/fixtures/rules", fixtureDir);
-            });
-
-            afterEach(() => {
-                sinon.verifyAndRestore();
-            });
-
-            after(() => {
-                sh.rm("-r", fixtureDir);
-            });
-
-            it("should create config object when using baseConfig with extends", () => {
-                const customBaseConfig = {
-                    extends: path.resolve(__dirname, "../../fixtures/config-extends/array/.eslintrc")
-                };
-                const factory = new CascadingConfigArrayFactory({ baseConfig: customBaseConfig, useEslintrc: false });
-                const config = getConfig(factory);
-
-                assert.deepStrictEqual(config.env, {
-                    browser: false,
-                    es6: true,
-                    node: true
-                });
-                assert.deepStrictEqual(config.rules, {
-                    "no-empty": [1],
-                    "comma-dangle": [2],
-                    "no-console": [2]
-                });
-            });
-
-            it("should return the project config when called in current working directory", () => {
-                const factory = new CascadingConfigArrayFactory();
-                const actual = getConfig(factory);
-
-                assert.strictEqual(actual.rules.strict[1], "global");
-            });
-
-            it("should not retain configs from previous directories when called multiple times", () => {
-                const firstpath = path.resolve(__dirname, "../../fixtures/configurations/single-quotes/subdir/.eslintrc");
-                const secondpath = path.resolve(__dirname, "../../fixtures/configurations/single-quotes/.eslintrc");
-                const factory = new CascadingConfigArrayFactory();
-                let config;
-
-                config = getConfig(factory, firstpath);
-                assert.deepStrictEqual(config.rules["no-new"], [0]);
-                config = getConfig(factory, secondpath);
-                assert.deepStrictEqual(config.rules["no-new"], [1]);
-            });
-
-            it("should throw error when a configuration file doesn't exist", () => {
-                const configPath = path.resolve(__dirname, "../../fixtures/configurations/.eslintrc");
-                const factory = new CascadingConfigArrayFactory();
-
-                sinon.stub(fs, "readFileSync").throws(new Error());
-
-                assert.throws(() => {
-                    getConfig(factory, configPath);
-                }, "Cannot read config file");
-
-            });
-
-            it("should throw error when a configuration file is not require-able", () => {
-                const configPath = ".eslintrc";
-                const factory = new CascadingConfigArrayFactory();
-
-                sinon.stub(fs, "readFileSync").throws(new Error());
-
-                assert.throws(() => {
-                    getConfig(factory, configPath);
-                }, "Cannot read config file");
-
-            });
-
-            it("should cache config when the same directory is passed twice", () => {
-                const configPath = path.resolve(__dirname, "../../fixtures/configurations/single-quotes/.eslintrc");
-                const configArrayFactory = new ConfigArrayFactory();
-                const factory = new CascadingConfigArrayFactory({ configArrayFactory });
-
-                sinon.spy(configArrayFactory, "loadInDirectory");
-
-                // If cached this should be called only once
-                getConfig(factory, configPath);
-                const callcount = configArrayFactory.loadInDirectory.callcount;
-
-                getConfig(factory, configPath);
-
-                assert.strictEqual(configArrayFactory.loadInDirectory.callcount, callcount);
-            });
-
-            // make sure JS-style comments don't throw an error
-            it("should load the config file when there are JS-style comments in the text", () => {
-                const specificConfigPath = path.resolve(__dirname, "../../fixtures/configurations/comments.json");
-                const factory = new CascadingConfigArrayFactory({ specificConfigPath, useEslintrc: false });
-                const config = getConfig(factory);
-                const { semi, strict } = config.rules;
-
-                assert.deepStrictEqual(semi, [1]);
-                assert.deepStrictEqual(strict, [0]);
-            });
-
-            // make sure YAML files work correctly
-            it("should load the config file when a YAML file is used", () => {
-                const specificConfigPath = path.resolve(__dirname, "../../fixtures/configurations/env-browser.yaml");
-                const factory = new CascadingConfigArrayFactory({ specificConfigPath, useEslintrc: false });
-                const config = getConfig(factory);
-                const { "no-alert": noAlert, "no-undef": noUndef } = config.rules;
-
-                assert.deepStrictEqual(noAlert, [0]);
-                assert.deepStrictEqual(noUndef, [2]);
-            });
-
-            it("should contain the correct value for parser when a custom parser is specified", () => {
-                const configPath = path.resolve(__dirname, "../../fixtures/configurations/parser/.eslintrc.json");
-                const factory = new CascadingConfigArrayFactory();
-                const config = getConfig(factory, configPath);
-
-                assert.strictEqual(config.parser, path.resolve(path.dirname(configPath), "./custom.js"));
-            });
-
-            /*
-             * Configuration hierarchy ---------------------------------------------
-             * https://github.com/eslint/eslint/issues/3915
-             */
-            it("should correctly merge environment settings", () => {
-                const factory = new CascadingConfigArrayFactory({ useEslintrc: true });
-                const file = getFixturePath("envs", "sub", "foo.js");
-                const expected = {
-                    rules: {},
-                    env: {
-                        browser: true,
-                        node: false
-                    },
-                    ignorePatterns: cwdIgnorePatterns
-                };
-                const actual = getConfig(factory, file);
-
-                assertConfigsEqual(actual, expected);
-            });
-
-            // Default configuration - blank
-            it("should return a blank config when using no .eslintrc", () => {
-                const factory = new CascadingConfigArrayFactory({ useEslintrc: false });
-                const file = getFixturePath("broken", "console-wrong-quotes.js");
-                const expected = {
-                    rules: {},
-                    globals: {},
-                    env: {},
-                    ignorePatterns: cwdIgnorePatterns
-                };
-                const actual = getConfig(factory, file);
-
-                assertConfigsEqual(actual, expected);
-            });
-
-            it("should return a blank config when baseConfig is set to false and no .eslintrc", () => {
-                const factory = new CascadingConfigArrayFactory({ baseConfig: false, useEslintrc: false });
-                const file = getFixturePath("broken", "console-wrong-quotes.js");
-                const expected = {
-                    rules: {},
-                    globals: {},
-                    env: {},
-                    ignorePatterns: cwdIgnorePatterns
-                };
-                const actual = getConfig(factory, file);
-
-                assertConfigsEqual(actual, expected);
-            });
-
-            // No default configuration
-            it("should return an empty config when not using .eslintrc", () => {
-                const factory = new CascadingConfigArrayFactory({ useEslintrc: false });
-                const file = getFixturePath("broken", "console-wrong-quotes.js");
-                const actual = getConfig(factory, file);
-
-                assertConfigsEqual(actual, { ignorePatterns: cwdIgnorePatterns });
-            });
-
-            it("should return a modified config when baseConfig is set to an object and no .eslintrc", () => {
-                const factory = new CascadingConfigArrayFactory({
-                    baseConfig: {
-                        env: {
-                            node: true
-                        },
-                        rules: {
-                            quotes: [2, "single"]
-                        }
-                    },
-                    useEslintrc: false
-                });
-                const file = getFixturePath("broken", "console-wrong-quotes.js");
-                const expected = {
-                    env: {
-                        node: true
-                    },
-                    rules: {
-                        quotes: [2, "single"]
-                    },
-                    ignorePatterns: cwdIgnorePatterns
-                };
-                const actual = getConfig(factory, file);
-
-                assertConfigsEqual(actual, expected);
-            });
-
-            it("should return a modified config without plugin rules enabled when baseConfig is set to an object with plugin and no .eslintrc", () => {
-                const factory = new CascadingConfigArrayFactory({
-                    baseConfig: {
-                        env: {
-                            node: true
-                        },
-                        rules: {
-                            quotes: [2, "single"]
-                        },
-                        plugins: ["example-with-rules-config"]
-                    },
-                    cwd: getFixturePath("plugins"),
-                    useEslintrc: false
-                });
-                const file = getFixturePath("broken", "plugins", "console-wrong-quotes.js");
-                const expected = {
-                    env: {
-                        node: true
-                    },
-                    plugins: ["example-with-rules-config"],
-                    rules: {
-                        quotes: [2, "single"]
-                    }
-                };
-                const actual = getConfig(factory, file);
-
-                assertConfigsEqual(actual, expected);
-            });
-
-            // Project configuration - second level .eslintrc
-            it("should merge configs when local .eslintrc overrides parent .eslintrc", () => {
-                const factory = new CascadingConfigArrayFactory();
-                const file = getFixturePath("broken", "subbroken", "console-wrong-quotes.js");
-                const expected = {
-                    env: {
-                        node: true
-                    },
-                    rules: {
-                        "no-console": [1],
-                        quotes: [2, "single"]
-                    },
-                    ignorePatterns: cwdIgnorePatterns
-                };
-                const actual = getConfig(factory, file);
-
-                assertConfigsEqual(actual, expected);
-            });
-
-            // Project configuration - third level .eslintrc
-            it("should merge configs when local .eslintrc overrides parent and grandparent .eslintrc", () => {
-                const factory = new CascadingConfigArrayFactory();
-                const file = getFixturePath("broken", "subbroken", "subsubbroken", "console-wrong-quotes.js");
-                const expected = {
-                    env: {
-                        node: true
-                    },
-                    rules: {
-                        "no-console": [0],
-                        quotes: [1, "double"]
-                    },
-                    ignorePatterns: cwdIgnorePatterns
-                };
-                const actual = getConfig(factory, file);
-
-                assertConfigsEqual(actual, expected);
-            });
-
-            // Project configuration - root set in second level .eslintrc
-            it("should not return or traverse configurations in parents of config with root:true", () => {
-                const factory = new CascadingConfigArrayFactory();
-                const file = getFixturePath("root-true", "parent", "root", "wrong-semi.js");
-                const expected = {
-                    rules: {
-                        semi: [2, "never"]
-                    },
-                    ignorePatterns: cwdIgnorePatterns
-                };
-                const actual = getConfig(factory, file);
-
-                assertConfigsEqual(actual, expected);
-            });
-
-            // Project configuration - root set in second level .eslintrc
-            it("should return project config when called with a relative path from a subdir", () => {
-                const factory = new CascadingConfigArrayFactory({ cwd: getFixturePath("root-true", "parent", "root", "subdir") });
-                const dir = ".";
-                const expected = {
-                    rules: {
-                        semi: [2, "never"]
-                    }
-                };
-                const actual = getConfig(factory, dir);
-
-                assertConfigsEqual(actual, expected);
-            });
-
-            // Command line configuration - --config with first level .eslintrc
-            it("should merge command line config when config file adds to local .eslintrc", () => {
-                const factory = new CascadingConfigArrayFactory({
-                    specificConfigPath: getFixturePath("broken", "add-conf.yaml")
-                });
-                const file = getFixturePath("broken", "console-wrong-quotes.js");
-                const expected = {
-                    env: {
-                        node: true
-                    },
-                    rules: {
-                        quotes: [2, "double"],
-                        semi: [1, "never"]
-                    },
-                    ignorePatterns: cwdIgnorePatterns
-                };
-                const actual = getConfig(factory, file);
-
-                assertConfigsEqual(actual, expected);
-            });
-
-            // Command line configuration - --config with first level .eslintrc
-            it("should merge command line config when config file overrides local .eslintrc", () => {
-                const factory = new CascadingConfigArrayFactory({
-                    specificConfigPath: getFixturePath("broken", "override-conf.yaml")
-                });
-                const file = getFixturePath("broken", "console-wrong-quotes.js");
-                const expected = {
-                    env: {
-                        node: true
-                    },
-                    rules: {
-                        quotes: [0, "double"]
-                    },
-                    ignorePatterns: cwdIgnorePatterns
-                };
-                const actual = getConfig(factory, file);
-
-                assertConfigsEqual(actual, expected);
-            });
-
-            // Command line configuration - --config with second level .eslintrc
-            it("should merge command line config when config file adds to local and parent .eslintrc", () => {
-                const factory = new CascadingConfigArrayFactory({
-                    specificConfigPath: getFixturePath("broken", "add-conf.yaml")
-                });
-                const file = getFixturePath("broken", "subbroken", "console-wrong-quotes.js");
-                const expected = {
-                    env: {
-                        node: true
-                    },
-                    rules: {
-                        quotes: [2, "single"],
-                        "no-console": [1],
-                        semi: [1, "never"]
-                    },
-                    ignorePatterns: cwdIgnorePatterns
-                };
-                const actual = getConfig(factory, file);
-
-                assertConfigsEqual(actual, expected);
-            });
-
-            // Command line configuration - --config with second level .eslintrc
-            it("should merge command line config when config file overrides local and parent .eslintrc", () => {
-                const factory = new CascadingConfigArrayFactory({
-                    specificConfigPath: getFixturePath("broken", "override-conf.yaml")
-                });
-                const file = getFixturePath("broken", "subbroken", "console-wrong-quotes.js");
-                const expected = {
-                    env: {
-                        node: true
-                    },
-                    rules: {
-                        quotes: [0, "single"],
-                        "no-console": [1]
-                    },
-                    ignorePatterns: cwdIgnorePatterns
-                };
-                const actual = getConfig(factory, file);
-
-                assertConfigsEqual(actual, expected);
-            });
-
-            // Command line configuration - --rule with --config and first level .eslintrc
-            it("should merge command line config and rule when rule and config file overrides local .eslintrc", () => {
-                const factory = new CascadingConfigArrayFactory({
-                    cliConfig: {
-                        rules: {
-                            quotes: [1, "double"]
-                        }
-                    },
-                    specificConfigPath: getFixturePath("broken", "override-conf.yaml")
-                });
-                const file = getFixturePath("broken", "console-wrong-quotes.js");
-                const expected = {
-                    env: {
-                        node: true
-                    },
-                    rules: {
-                        quotes: [1, "double"]
-                    },
-                    ignorePatterns: cwdIgnorePatterns
-                };
-                const actual = getConfig(factory, file);
-
-                assertConfigsEqual(actual, expected);
-            });
-
-            // Command line configuration - --plugin
-            it("should merge command line plugin with local .eslintrc", () => {
-                const factory = new CascadingConfigArrayFactory({
-                    cliConfig: {
-                        plugins: ["another-plugin"]
-                    },
-                    cwd: getFixturePath("plugins"),
-                    resolvePluginsRelativeTo: getFixturePath("plugins")
-                });
-                const file = getFixturePath("broken", "plugins", "console-wrong-quotes.js");
-                const expected = {
-                    env: {
-                        node: true
-                    },
-                    plugins: [
-                        "example",
-                        "another-plugin"
-                    ],
-                    rules: {
-                        quotes: [2, "double"]
-                    }
-                };
-                const actual = getConfig(factory, file);
-
-                assertConfigsEqual(actual, expected);
-            });
-
-
-            it("should merge multiple different config file formats", () => {
-                const factory = new CascadingConfigArrayFactory();
-                const file = getFixturePath("fileexts/subdir/subsubdir/foo.js");
-                const expected = {
-                    env: {
-                        browser: true
-                    },
-                    rules: {
-                        semi: [2, "always"],
-                        eqeqeq: [2]
-                    },
-                    ignorePatterns: cwdIgnorePatterns
-                };
-                const actual = getConfig(factory, file);
-
-                assertConfigsEqual(actual, expected);
-            });
-
-
-            it("should load user config globals", () => {
-                const configPath = path.resolve(__dirname, "../../fixtures/globals/conf.yaml");
-                const factory = new CascadingConfigArrayFactory({ specificConfigPath: configPath, useEslintrc: false });
-                const expected = {
-                    globals: {
-                        foo: true
-                    },
-                    ignorePatterns: cwdIgnorePatterns
-                };
-                const actual = getConfig(factory, configPath);
-
-                assertConfigsEqual(actual, expected);
-            });
-
-            it("should not load disabled environments", () => {
-                const configPath = path.resolve(__dirname, "../../fixtures/environments/disable.yaml");
-                const factory = new CascadingConfigArrayFactory({ specificConfigPath: configPath, useEslintrc: false });
-                const config = getConfig(factory, configPath);
-
-                assert.isUndefined(config.globals.window);
-            });
-
-            it("should gracefully handle empty files", () => {
-                const configPath = path.resolve(__dirname, "../../fixtures/configurations/env-node.json");
-                const factory = new CascadingConfigArrayFactory({ specificConfigPath: configPath });
-
-                getConfig(factory, path.resolve(__dirname, "../../fixtures/configurations/empty/empty.json"));
-            });
-
-            // Meaningful stack-traces
-            it("should include references to where an `extends` configuration was loaded from", () => {
-                const configPath = path.resolve(__dirname, "../../fixtures/config-extends/error.json");
-
-                assert.throws(() => {
-                    const factory = new CascadingConfigArrayFactory({ useEslintrc: false, specificConfigPath: configPath });
-
-                    getConfig(factory, configPath);
-                }, /Referenced from:.*?error\.json/u);
-            });
-
-            // Keep order with the last array element taking highest precedence
-            it("should make the last element in an array take the highest precedence", () => {
-                const configPath = path.resolve(__dirname, "../../fixtures/config-extends/array/.eslintrc");
-                const factory = new CascadingConfigArrayFactory({ useEslintrc: false, specificConfigPath: configPath });
-                const expected = {
-                    rules: { "no-empty": [1], "comma-dangle": [2], "no-console": [2] },
-                    env: { browser: false, node: true, es6: true },
-                    ignorePatterns: cwdIgnorePatterns
-                };
-                const actual = getConfig(factory, configPath);
-
-                assertConfigsEqual(actual, expected);
-            });
-
-            describe("with env in a child configuration file", () => {
-                it("should not overwrite parserOptions of the parent with env of the child", () => {
-                    const factory = new CascadingConfigArrayFactory();
-                    const targetPath = getFixturePath("overwrite-ecmaFeatures", "child", "foo.js");
-                    const expected = {
-                        rules: {},
-                        env: { commonjs: true },
-                        parserOptions: { ecmaFeatures: { globalReturn: false } },
-                        ignorePatterns: cwdIgnorePatterns
-                    };
-                    const actual = getConfig(factory, targetPath);
-
-                    assertConfigsEqual(actual, expected);
-                });
-            });
-
-            describe("personal config file within home directory", () => {
-                const {
-                    CascadingConfigArrayFactory: StubbedCascadingConfigArrayFactory
-                } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
-                    files: {
-                        "eslint/fixtures/config-hierarchy": DIRECTORY_CONFIG_HIERARCHY
-                    }
-                });
-
-                /**
-                 * Returns the path inside of the fixture directory.
-                 * @param {...string} args file path segments.
-                 * @returns {string} The path inside the fixture directory.
-                 * @private
-                 */
-                function getFakeFixturePath(...args) {
-                    return path.join(process.cwd(), "eslint", "fixtures", "config-hierarchy", ...args);
-                }
-
-                it("should load the personal config if no local config was found", () => {
-                    const projectPath = getFakeFixturePath("personal-config", "project-without-config");
-                    const homePath = getFakeFixturePath("personal-config", "home-folder");
-                    const filePath = getFakeFixturePath("personal-config", "project-without-config", "foo.js");
-                    const factory = new StubbedCascadingConfigArrayFactory({ cwd: projectPath });
-
-                    mockOsHomedir(homePath);
-
-                    const actual = getConfig(factory, filePath);
-                    const expected = {
-                        rules: {
-                            "home-folder-rule": [2]
-                        }
-                    };
-
-                    assertConfigsEqual(actual, expected);
-                });
-
-                it("should ignore the personal config if a local config was found", () => {
-                    const projectPath = getFakeFixturePath("personal-config", "home-folder", "project");
-                    const homePath = getFakeFixturePath("personal-config", "home-folder");
-                    const filePath = getFakeFixturePath("personal-config", "home-folder", "project", "foo.js");
-                    const factory = new StubbedCascadingConfigArrayFactory({ cwd: projectPath });
-
-                    mockOsHomedir(homePath);
-
-                    const actual = getConfig(factory, filePath);
-                    const expected = {
-                        rules: {
-                            "project-level-rule": [2]
-                        }
-                    };
-
-                    assertConfigsEqual(actual, expected);
-                });
-
-                it("should ignore the personal config if config is passed through cli", () => {
-                    const configPath = getFakeFixturePath("quotes-error.json");
-                    const projectPath = getFakeFixturePath("personal-config", "project-without-config");
-                    const homePath = getFakeFixturePath("personal-config", "home-folder");
-                    const filePath = getFakeFixturePath("personal-config", "project-without-config", "foo.js");
-                    const factory = new StubbedCascadingConfigArrayFactory({
-                        cwd: projectPath,
-                        specificConfigPath: configPath
-                    });
-
-                    mockOsHomedir(homePath);
-
-                    const actual = getConfig(factory, filePath);
-                    const expected = {
-                        rules: {
-                            quotes: [2, "double"]
-                        }
-                    };
-
-                    assertConfigsEqual(actual, expected);
-                });
-
-                it("should still load the project config if the current working directory is the same as the home folder", () => {
-                    const projectPath = getFakeFixturePath("personal-config", "project-with-config");
-                    const filePath = getFakeFixturePath("personal-config", "project-with-config", "subfolder", "foo.js");
-                    const factory = new StubbedCascadingConfigArrayFactory({ cwd: projectPath });
-
-                    mockOsHomedir(projectPath);
-
-                    const actual = getConfig(factory, filePath);
-                    const expected = {
-                        rules: {
-                            "project-level-rule": [2],
-                            "subfolder-level-rule": [2]
-                        }
-                    };
-
-                    assertConfigsEqual(actual, expected);
-                });
-            });
-
-            describe("when no local or personal config is found", () => {
-                const {
-                    CascadingConfigArrayFactory: StubbedCascadingConfigArrayFactory
-                } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
-                    files: {
-                        "eslint/fixtures/config-hierarchy": DIRECTORY_CONFIG_HIERARCHY
-                    }
-                });
-
-                /**
-                 * Returns the path inside of the fixture directory.
-                 * @param {...string} args file path segments.
-                 * @returns {string} The path inside the fixture directory.
-                 * @private
-                 */
-                function getFakeFixturePath(...args) {
-                    return path.join(process.cwd(), "eslint", "fixtures", "config-hierarchy", ...args);
-                }
-
-                it("should throw an error if no local config and no personal config was found", () => {
-                    const projectPath = getFakeFixturePath("personal-config", "project-without-config");
-                    const homePath = getFakeFixturePath("personal-config", "folder-does-not-exist");
-                    const filePath = getFakeFixturePath("personal-config", "project-without-config", "foo.js");
-                    const factory = new StubbedCascadingConfigArrayFactory({ cwd: projectPath });
-
-                    mockOsHomedir(homePath);
-
-                    assert.throws(() => {
-                        getConfig(factory, filePath);
-                    }, "No ESLint configuration found");
-                });
-
-                it("should throw an error if no local config was found and ~/package.json contains no eslintConfig section", () => {
-                    const projectPath = getFakeFixturePath("personal-config", "project-without-config");
-                    const homePath = getFakeFixturePath("personal-config", "home-folder-with-packagejson");
-                    const filePath = getFakeFixturePath("personal-config", "project-without-config", "foo.js");
-                    const factory = new StubbedCascadingConfigArrayFactory({ cwd: projectPath });
-
-                    mockOsHomedir(homePath);
-
-                    assert.throws(() => {
-                        getConfig(factory, filePath);
-                    }, "No ESLint configuration found");
-                });
-
-                it("should not throw an error if no local config and no personal config was found but useEslintrc is false", () => {
-                    const projectPath = getFakeFixturePath("personal-config", "project-without-config");
-                    const homePath = getFakeFixturePath("personal-config", "folder-does-not-exist");
-                    const filePath = getFakeFixturePath("personal-config", "project-without-config", "foo.js");
-                    const factory = new StubbedCascadingConfigArrayFactory({ cwd: projectPath, useEslintrc: false });
-
-                    mockOsHomedir(homePath);
-
-                    getConfig(factory, filePath);
-                });
-
-                it("should not throw an error if no local config and no personal config was found but rules are specified", () => {
-                    const projectPath = getFakeFixturePath("personal-config", "project-without-config");
-                    const homePath = getFakeFixturePath("personal-config", "folder-does-not-exist");
-                    const filePath = getFakeFixturePath("personal-config", "project-without-config", "foo.js");
-                    const factory = new StubbedCascadingConfigArrayFactory({
-                        cliConfig: {
-                            rules: { quotes: [2, "single"] }
-                        },
-                        cwd: projectPath
-                    });
-
-                    mockOsHomedir(homePath);
-
-                    getConfig(factory, filePath);
-                });
-
-                it("should not throw an error if no local config and no personal config was found but baseConfig is specified", () => {
-                    const projectPath = getFakeFixturePath("personal-config", "project-without-config");
-                    const homePath = getFakeFixturePath("personal-config", "folder-does-not-exist");
-                    const filePath = getFakeFixturePath("personal-config", "project-without-config", "foo.js");
-                    const factory = new StubbedCascadingConfigArrayFactory({ baseConfig: {}, cwd: projectPath });
-
-                    mockOsHomedir(homePath);
-
-                    getConfig(factory, filePath);
-                });
-            });
-
-            describe("with overrides", () => {
-                const {
-                    CascadingConfigArrayFactory: StubbedCascadingConfigArrayFactory
-                } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
-                    files: {
-                        "eslint/fixtures/config-hierarchy": DIRECTORY_CONFIG_HIERARCHY
-                    }
-                });
-
-                /**
-                 * Returns the path inside of the fixture directory.
-                 * @param {...string} pathSegments One or more path segments, in order of depth, shallowest first
-                 * @returns {string} The path inside the fixture directory.
-                 * @private
-                 */
-                function getFakeFixturePath(...pathSegments) {
-                    return path.join(process.cwd(), "eslint", "fixtures", "config-hierarchy", ...pathSegments);
-                }
-
-                it("should merge override config when the pattern matches the file name", () => {
-                    const factory = new StubbedCascadingConfigArrayFactory({});
-                    const targetPath = getFakeFixturePath("overrides", "foo.js");
-                    const expected = {
-                        rules: {
-                            quotes: [2, "single"],
-                            "no-else-return": [0],
-                            "no-unused-vars": [1],
-                            semi: [1, "never"]
-                        }
-                    };
-                    const actual = getConfig(factory, targetPath);
-
-                    assertConfigsEqual(actual, expected);
-                });
-
-                it("should merge override config when the pattern matches the file path relative to the config file", () => {
-                    const factory = new StubbedCascadingConfigArrayFactory({});
-                    const targetPath = getFakeFixturePath("overrides", "child", "child-one.js");
-                    const expected = {
-                        rules: {
-                            curly: ["error", "multi", "consistent"],
-                            "no-else-return": [0],
-                            "no-unused-vars": [1],
-                            quotes: [2, "double"],
-                            semi: [1, "never"]
-                        }
-                    };
-                    const actual = getConfig(factory, targetPath);
-
-                    assertConfigsEqual(actual, expected);
-                });
-
-                it("should not merge override config when the pattern matches the absolute file path", () => {
-                    const resolvedPath = path.resolve(__dirname, "../../fixtures/config-hierarchy/overrides/bar.js");
-
-                    assert.throws(() => new StubbedCascadingConfigArrayFactory({
-                        baseConfig: {
-                            overrides: [{
-                                files: resolvedPath,
-                                rules: {
-                                    quotes: [1, "double"]
-                                }
-                            }]
-                        },
-                        useEslintrc: false
-                    }), /Invalid override pattern/u);
-                });
-
-                it("should not merge override config when the pattern traverses up the directory tree", () => {
-                    const parentPath = "overrides/../**/*.js";
-
-                    assert.throws(() => new StubbedCascadingConfigArrayFactory({
-                        baseConfig: {
-                            overrides: [{
-                                files: parentPath,
-                                rules: {
-                                    quotes: [1, "single"]
-                                }
-                            }]
-                        },
-                        useEslintrc: false
-                    }), /Invalid override pattern/u);
-                });
-
-                it("should merge all local configs (override and non-override) before non-local configs", () => {
-                    const factory = new StubbedCascadingConfigArrayFactory({});
-                    const targetPath = getFakeFixturePath("overrides", "two", "child-two.js");
-                    const expected = {
-                        rules: {
-                            "no-console": [0],
-                            "no-else-return": [0],
-                            "no-unused-vars": [2],
-                            quotes: [2, "double"],
-                            semi: [2, "never"]
-                        }
-                    };
-                    const actual = getConfig(factory, targetPath);
-
-                    assertConfigsEqual(actual, expected);
-                });
-
-                it("should apply overrides in parent .eslintrc over non-override rules in child .eslintrc", () => {
-                    const targetPath = getFakeFixturePath("overrides", "three", "foo.js");
-                    const factory = new StubbedCascadingConfigArrayFactory({
-                        cwd: getFakeFixturePath("overrides"),
-                        baseConfig: {
-                            overrides: [
-                                {
-                                    files: "three/**/*.js",
-                                    rules: {
-                                        "semi-style": [2, "last"]
-                                    }
-                                }
-                            ]
-                        },
-                        useEslintrc: false
-                    });
-                    const expected = {
-                        rules: {
-                            "semi-style": [2, "last"]
-                        }
-                    };
-                    const actual = getConfig(factory, targetPath);
-
-                    assertConfigsEqual(actual, expected);
-                });
-
-                it("should apply overrides if all glob patterns match", () => {
-                    const targetPath = getFakeFixturePath("overrides", "one", "child-one.js");
-                    const factory = new StubbedCascadingConfigArrayFactory({
-                        cwd: getFakeFixturePath("overrides"),
-                        baseConfig: {
-                            overrides: [{
-                                files: ["one/**/*", "*.js"],
-                                rules: {
-                                    quotes: [2, "single"]
-                                }
-                            }]
-                        },
-                        useEslintrc: false
-                    });
-                    const expected = {
-                        rules: {
-                            quotes: [2, "single"]
-                        }
-                    };
-                    const actual = getConfig(factory, targetPath);
-
-                    assertConfigsEqual(actual, expected);
-                });
-
-                it("should apply overrides even if some glob patterns do not match", () => {
-                    const targetPath = getFakeFixturePath("overrides", "one", "child-one.js");
-                    const factory = new StubbedCascadingConfigArrayFactory({
-                        cwd: getFakeFixturePath("overrides"),
-                        baseConfig: {
-                            overrides: [{
-                                files: ["one/**/*", "*two.js"],
-                                rules: {
-                                    quotes: [2, "single"]
-                                }
-                            }]
-                        },
-                        useEslintrc: false
-                    });
-                    const expected = {
-                        rules: {
-                            quotes: [2, "single"]
-                        }
-                    };
-                    const actual = getConfig(factory, targetPath);
-
-                    assertConfigsEqual(actual, expected);
-                });
-
-                it("should not apply overrides if any excluded glob patterns match", () => {
-                    const targetPath = getFakeFixturePath("overrides", "one", "child-one.js");
-                    const factory = new StubbedCascadingConfigArrayFactory({
-                        cwd: getFakeFixturePath("overrides"),
-                        baseConfig: {
-                            overrides: [{
-                                files: "one/**/*",
-                                excludedFiles: ["two/**/*", "*one.js"],
-                                rules: {
-                                    quotes: [2, "single"]
-                                }
-                            }]
-                        },
-                        useEslintrc: false
-                    });
-                    const expected = {
-                        rules: {}
-                    };
-                    const actual = getConfig(factory, targetPath);
-
-                    assertConfigsEqual(actual, expected);
-                });
-
-                it("should apply overrides if all excluded glob patterns fail to match", () => {
-                    const targetPath = getFakeFixturePath("overrides", "one", "child-one.js");
-                    const factory = new StubbedCascadingConfigArrayFactory({
-                        cwd: getFakeFixturePath("overrides"),
-                        baseConfig: {
-                            overrides: [{
-                                files: "one/**/*",
-                                excludedFiles: ["two/**/*", "*two.js"],
-                                rules: {
-                                    quotes: [2, "single"]
-                                }
-                            }]
-                        },
-                        useEslintrc: false
-                    });
-                    const expected = {
-                        rules: {
-                            quotes: [2, "single"]
-                        }
-                    };
-                    const actual = getConfig(factory, targetPath);
-
-                    assertConfigsEqual(actual, expected);
-                });
-
-                it("should cascade", () => {
-                    const targetPath = getFakeFixturePath("overrides", "foo.js");
-                    const factory = new StubbedCascadingConfigArrayFactory({
-                        cwd: getFakeFixturePath("overrides"),
-                        baseConfig: {
-                            overrides: [
-                                {
-                                    files: "foo.js",
-                                    rules: {
-                                        semi: [2, "never"],
-                                        quotes: [2, "single"]
-                                    }
-                                },
-                                {
-                                    files: "foo.js",
-                                    rules: {
-                                        semi: [2, "never"],
-                                        quotes: [2, "double"]
-                                    }
-                                }
-                            ]
-                        },
-                        useEslintrc: false
-                    });
-                    const expected = {
-                        rules: {
-                            semi: [2, "never"],
-                            quotes: [2, "double"]
-                        }
-                    };
-                    const actual = getConfig(factory, targetPath);
-
-                    assertConfigsEqual(actual, expected);
-                });
-            });
-
-            describe("deprecation warnings", () => {
-                const cwd = path.resolve(__dirname, "../../fixtures/config-file/");
-                let warning = null;
-
-                /**
-                 * Store a reported warning object if that code starts with `ESLINT_`.
-                 * @param {{code:string, message:string}} w The warning object to store.
-                 * @returns {void}
-                 */
-                function onWarning(w) {
-                    if (w.code.startsWith("ESLINT_")) {
-                        warning = w;
-                    }
-                }
-
-                /** @type {CascadingConfigArrayFactory} */
-                let factory;
-
-                beforeEach(() => {
-                    factory = new CascadingConfigArrayFactory({ cwd });
-                    warning = null;
-                    process.on("warning", onWarning);
-                });
-                afterEach(() => {
-                    process.removeListener("warning", onWarning);
-                });
-
-                it("should emit a deprecation warning if 'ecmaFeatures' is given.", async () => {
-                    getConfig(factory, "ecma-features/test.js");
-
-                    // Wait for "warning" event.
-                    await nextTick();
-
-                    assert.notStrictEqual(warning, null);
-                    assert.strictEqual(
-                        warning.message,
-                        `The 'ecmaFeatures' config file property is deprecated and has no effect. (found in "ecma-features${path.sep}.eslintrc.yml")`
-                    );
-                });
-            });
-        });
-    });
-
-    describe("'clearCache()' method should clear cache.", () => {
-        describe("with a '.eslintrc.js' file", () => {
-            const root = path.join(os.tmpdir(), "eslint/cli-engine/cascading-config-array-factory");
-            const files = {
-                ".eslintrc.js": ""
-            };
-            const {
-                CascadingConfigArrayFactory
-            } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({ cwd: () => root, files });
-
-            /** @type {Map<string, Object>} */
-            let additionalPluginPool;
-
-            /** @type {CascadingConfigArrayFactory} */
-            let factory;
-
-            beforeEach(() => {
-                additionalPluginPool = new Map();
-                factory = new CascadingConfigArrayFactory({
-                    additionalPluginPool,
-                    cliConfig: { plugins: ["test"] }
-                });
-            });
-
-            it("should use cached instance.", () => {
-                const one = factory.getConfigArrayForFile("a.js");
-                const two = factory.getConfigArrayForFile("a.js");
-
-                assert.strictEqual(one, two);
-            });
-
-            it("should not use cached instance if 'clearCache()' method is called after first config is retrieved", () => {
-                const one = factory.getConfigArrayForFile("a.js");
-
-                factory.clearCache();
-                const two = factory.getConfigArrayForFile("a.js");
-
-                assert.notStrictEqual(one, two);
-            });
-
-            it("should have a loading error in CLI config.", () => {
-                const config = factory.getConfigArrayForFile("a.js");
-
-                assert.strictEqual(config[2].plugins.test.definition, null);
-            });
-
-            it("should not have a loading error in CLI config after adding 'test' plugin to the additional plugin pool then calling 'clearCache()'.", () => {
-                factory.getConfigArrayForFile("a.js");
-
-                additionalPluginPool.set("test", { configs: { name: "test" } });
-                factory.clearCache();
-
-                // Check.
-                const config = factory.getConfigArrayForFile("a.js");
-
-                assert.deepStrictEqual(
-                    config[2].plugins.test.definition,
-                    {
-                        configs: { name: "test" },
-                        environments: {},
-                        processors: {},
-                        rules: {}
-                    }
-                );
-            });
-        });
-    });
-});
index fb3c365bbbf8e7cf3abfc88753b152985f2d9690..9e7c49c84d7cc1113cfb46de09804a33b6dc542e 100644 (file)
 const assert = require("chai").assert,
     path = require("path"),
     sinon = require("sinon"),
-    leche = require("leche"),
     shell = require("shelljs"),
     fs = require("fs"),
     os = require("os"),
     hash = require("../../../lib/cli-engine/hash"),
-    { CascadingConfigArrayFactory } = require("../../../lib/cli-engine/cascading-config-array-factory"),
-    { unIndent, defineCLIEngineWithInMemoryFileSystem } = require("../../_utils");
+    { CascadingConfigArrayFactory } = require("@eslint/eslintrc/lib/cascading-config-array-factory"),
+    { unIndent, createCustomTeardown } = require("../../_utils");
 
 const proxyquire = require("proxyquire").noCallThru().noPreserveCache();
 const fCache = require("file-entry-cache");
@@ -81,7 +80,15 @@ describe("CLIEngine", () => {
     }
 
     // copy into clean area so as not to get "infected" by this project's .eslintrc files
-    before(() => {
+    before(function() {
+
+        /*
+         * GitHub Actions Windows and macOS runners occasionally exhibit
+         * extremely slow filesystem operations, during which copying fixtures
+         * exceeds the default test timeout, so raise it just for this hook.
+         * Mocha uses `this` to set timeouts on an individual hook level.
+         */
+        this.timeout(60 * 1000); // eslint-disable-line no-invalid-this
         shell.mkdir("-p", fixtureDir);
         shell.cp("-r", "./tests/fixtures/.", fixtureDir);
     });
@@ -822,7 +829,7 @@ describe("CLIEngine", () => {
             engine = new CLIEngine({
                 parser: "espree",
                 parserOptions: {
-                    ecmaVersion: 2020
+                    ecmaVersion: 2021
                 },
                 useEslintrc: false
             });
@@ -1286,6 +1293,10 @@ describe("CLIEngine", () => {
 
         it("should throw an error when all given files are ignored", () => {
 
+            engine = new CLIEngine({
+                ignorePath: getFixturePath(".eslintignore")
+            });
+
             assert.throws(() => {
                 engine.executeOnFiles(["tests/fixtures/cli-engine/"]);
             }, "All files matched by 'tests/fixtures/cli-engine/' are ignored.");
@@ -2972,26 +2983,34 @@ describe("CLIEngine", () => {
         });
 
         describe("a config file setting should have higher priority than a shareable config file's settings always; https://github.com/eslint/eslint/issues/11510", () => {
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: path.join(os.tmpdir(), "cli-engine/11510"),
+                files: {
+                    "no-console-error-in-overrides.json": {
+                        overrides: [{
+                            files: ["*.js"],
+                            rules: { "no-console": "error" }
+                        }]
+                    },
+                    ".eslintrc.json": {
+                        extends: "./no-console-error-in-overrides.json",
+                        rules: { "no-console": "off" }
+                    },
+                    "a.js": "console.log();"
+                }
+            });
+
             beforeEach(() => {
-                ({ CLIEngine } = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => path.join(os.tmpdir(), "cli-engine/11510"),
-                    files: {
-                        "no-console-error-in-overrides.json": JSON.stringify({
-                            overrides: [{
-                                files: ["*.js"],
-                                rules: { "no-console": "error" }
-                            }]
-                        }),
-                        ".eslintrc.json": JSON.stringify({
-                            extends: "./no-console-error-in-overrides.json",
-                            rules: { "no-console": "off" }
-                        }),
-                        "a.js": "console.log();"
-                    }
-                }));
-                engine = new CLIEngine();
+                engine = new CLIEngine({
+                    cwd: getPath()
+                });
+
+                return prepare();
             });
 
+            afterEach(cleanup);
+
             it("should not report 'no-console' error.", () => {
                 const { results } = engine.executeOnFiles("a.js");
 
@@ -3001,35 +3020,42 @@ describe("CLIEngine", () => {
         });
 
         describe("configs of plugin rules should be validated even if 'plugins' key doesn't exist; https://github.com/eslint/eslint/issues/11559", () => {
-            beforeEach(() => {
-                ({ CLIEngine } = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => path.join(os.tmpdir(), "cli-engine/11559"),
-                    files: {
-                        "node_modules/eslint-plugin-test/index.js": `
-                            exports.configs = {
-                                recommended: { plugins: ["test"] }
-                            };
-                            exports.rules = {
-                                foo: {
-                                    meta: { schema: [{ type: "number" }] },
-                                    create() { return {}; }
-                                }
-                            };
-                        `,
-                        ".eslintrc.json": JSON.stringify({
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: path.join(os.tmpdir(), "cli-engine/11559"),
+                files: {
+                    "node_modules/eslint-plugin-test/index.js": `
+                        exports.configs = {
+                            recommended: { plugins: ["test"] }
+                        };
+                        exports.rules = {
+                            foo: {
+                                meta: { schema: [{ type: "number" }] },
+                                create() { return {}; }
+                            }
+                        };
+                    `,
+                    ".eslintrc.json": {
 
-                            // Import via the recommended config.
-                            extends: "plugin:test/recommended",
+                        // Import via the recommended config.
+                        extends: "plugin:test/recommended",
 
-                            // Has invalid option.
-                            rules: { "test/foo": ["error", "invalid-option"] }
-                        }),
-                        "a.js": "console.log();"
-                    }
-                }));
-                engine = new CLIEngine();
+                        // Has invalid option.
+                        rules: { "test/foo": ["error", "invalid-option"] }
+                    },
+                    "a.js": "console.log();"
+                }
             });
 
+            beforeEach(() => {
+                engine = new CLIEngine({
+                    cwd: getPath()
+                });
+
+                return prepare();
+            });
+
+            afterEach(cleanup);
+
             it("should throw fatal error.", () => {
                 assert.throws(() => {
                     engine.executeOnFiles("a.js");
@@ -3038,11 +3064,11 @@ describe("CLIEngine", () => {
         });
 
         describe("'--fix-type' should not crash even if plugin rules exist; https://github.com/eslint/eslint/issues/11586", () => {
-            beforeEach(() => {
-                ({ CLIEngine } = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => path.join(os.tmpdir(), "cli-engine/11586"),
-                    files: {
-                        "node_modules/eslint-plugin-test/index.js": `
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: path.join(os.tmpdir(), "cli-engine/11586"),
+                files: {
+                    "node_modules/eslint-plugin-test/index.js": `
                             exports.rules = {
                                 "no-example": {
                                     meta: { type: "problem", fixable: "code" },
@@ -3062,16 +3088,27 @@ describe("CLIEngine", () => {
                                 }
                             };
                         `,
-                        ".eslintrc.json": JSON.stringify({
-                            plugins: ["test"],
-                            rules: { "test/no-example": "error" }
-                        }),
-                        "a.js": "example;"
-                    }
-                }));
-                engine = new CLIEngine({ fix: true, fixTypes: ["problem"] });
+                    ".eslintrc.json": {
+                        plugins: ["test"],
+                        rules: { "test/no-example": "error" }
+                    },
+                    "a.js": "example;"
+                }
+            });
+
+            beforeEach(() => {
+                engine = new CLIEngine({
+                    cwd: getPath(),
+                    fix: true,
+                    fixTypes: ["problem"]
+                });
+
+                return prepare();
             });
 
+            afterEach(cleanup);
+
+
             it("should not crash.", () => {
                 const { results } = engine.executeOnFiles("a.js");
 
@@ -3122,18 +3159,29 @@ describe("CLIEngine", () => {
                 `
             };
 
-            it("should lint only JavaScript blocks if '--ext' was not given.", () => {
-                CLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
+            let cleanup;
+
+            beforeEach(() => {
+                cleanup = () => {};
+            });
+
+            afterEach(() => cleanup());
+
+            it("should lint only JavaScript blocks if '--ext' was not given.", async () => {
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         ...commonFiles,
-                        ".eslintrc.json": JSON.stringify({
+                        ".eslintrc.json": {
                             plugins: ["markdown", "html"],
                             rules: { semi: "error" }
-                        })
+                        }
                     }
-                }).CLIEngine;
-                engine = new CLIEngine({ cwd: root });
+                });
+
+                cleanup = teardown.cleanup;
+                await teardown.prepare();
+                engine = new CLIEngine({ cwd: teardown.getPath() });
 
                 const { results } = engine.executeOnFiles(["test.md"]);
 
@@ -3143,18 +3191,25 @@ describe("CLIEngine", () => {
                 assert.strictEqual(results[0].messages[0].line, 2);
             });
 
-            it("should fix only JavaScript blocks if '--ext' was not given.", () => {
-                CLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
+            it("should fix only JavaScript blocks if '--ext' was not given.", async () => {
+
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         ...commonFiles,
-                        ".eslintrc.json": JSON.stringify({
+                        ".eslintrc.json": {
                             plugins: ["markdown", "html"],
                             rules: { semi: "error" }
-                        })
+                        }
                     }
-                }).CLIEngine;
-                engine = new CLIEngine({ cwd: root, fix: true });
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                engine = new CLIEngine({
+                    cwd: teardown.getPath(),
+                    fix: true
+                });
 
                 const { results } = engine.executeOnFiles(["test.md"]);
 
@@ -3176,18 +3231,24 @@ describe("CLIEngine", () => {
                 `);
             });
 
-            it("should lint HTML blocks as well with multiple processors if '--ext' option was given.", () => {
-                CLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
+            it("should lint HTML blocks as well with multiple processors if '--ext' option was given.", async () => {
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         ...commonFiles,
-                        ".eslintrc.json": JSON.stringify({
+                        ".eslintrc.json": {
                             plugins: ["markdown", "html"],
                             rules: { semi: "error" }
-                        })
+                        }
                     }
-                }).CLIEngine;
-                engine = new CLIEngine({ cwd: root, extensions: ["js", "html"] });
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                engine = new CLIEngine({
+                    cwd: teardown.getPath(),
+                    extensions: ["js", "html"]
+                });
 
                 const { results } = engine.executeOnFiles(["test.md"]);
 
@@ -3199,18 +3260,25 @@ describe("CLIEngine", () => {
                 assert.strictEqual(results[0].messages[1].line, 7);
             });
 
-            it("should fix HTML blocks as well with multiple processors if '--ext' option was given.", () => {
-                CLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
+            it("should fix HTML blocks as well with multiple processors if '--ext' option was given.", async () => {
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         ...commonFiles,
-                        ".eslintrc.json": JSON.stringify({
+                        ".eslintrc.json": {
                             plugins: ["markdown", "html"],
                             rules: { semi: "error" }
-                        })
+                        }
                     }
-                }).CLIEngine;
-                engine = new CLIEngine({ cwd: root, extensions: ["js", "html"], fix: true });
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                engine = new CLIEngine({
+                    cwd: teardown.getPath(),
+                    extensions: ["js", "html"],
+                    fix: true
+                });
 
                 const { results } = engine.executeOnFiles(["test.md"]);
 
@@ -3232,12 +3300,13 @@ describe("CLIEngine", () => {
                 `);
             });
 
-            it("should use overridden processor; should report HTML blocks but not fix HTML blocks if the processor for '*.html' didn't support autofix.", () => {
-                CLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
+            it("should use overridden processor; should report HTML blocks but not fix HTML blocks if the processor for '*.html' didn't support autofix.", async () => {
+
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         ...commonFiles,
-                        ".eslintrc.json": JSON.stringify({
+                        ".eslintrc.json": {
                             plugins: ["markdown", "html"],
                             rules: { semi: "error" },
                             overrides: [
@@ -3246,10 +3315,17 @@ describe("CLIEngine", () => {
                                     processor: "html/non-fixable" // supportsAutofix: false
                                 }
                             ]
-                        })
+                        }
                     }
-                }).CLIEngine;
-                engine = new CLIEngine({ cwd: root, extensions: ["js", "html"], fix: true });
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                engine = new CLIEngine({
+                    cwd: teardown.getPath(),
+                    extensions: ["js", "html"],
+                    fix: true
+                });
 
                 const { results } = engine.executeOnFiles(["test.md"]);
 
@@ -3274,12 +3350,13 @@ describe("CLIEngine", () => {
                 `);
             });
 
-            it("should use the config '**/*.html/*.js' to lint JavaScript blocks in HTML.", () => {
-                CLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
+            it("should use the config '**/*.html/*.js' to lint JavaScript blocks in HTML.", async () => {
+
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         ...commonFiles,
-                        ".eslintrc.json": JSON.stringify({
+                        ".eslintrc.json": {
                             plugins: ["markdown", "html"],
                             rules: { semi: "error" },
                             overrides: [
@@ -3300,10 +3377,16 @@ describe("CLIEngine", () => {
                                     }
                                 }
                             ]
-                        })
+                        }
                     }
-                }).CLIEngine;
-                engine = new CLIEngine({ cwd: root, extensions: ["js", "html"] });
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                engine = new CLIEngine({
+                    cwd: teardown.getPath(),
+                    extensions: ["js", "html"]
+                });
 
                 const { results } = engine.executeOnFiles(["test.md"]);
 
@@ -3315,12 +3398,13 @@ describe("CLIEngine", () => {
                 assert.strictEqual(results[0].messages[1].line, 7);
             });
 
-            it("should use the same config as one which has 'processor' property in order to lint blocks in HTML if the processor was legacy style.", () => {
-                CLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
+            it("should use the same config as one which has 'processor' property in order to lint blocks in HTML if the processor was legacy style.", async () => {
+
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         ...commonFiles,
-                        ".eslintrc.json": JSON.stringify({
+                        ".eslintrc.json": {
                             plugins: ["markdown", "html"],
                             rules: { semi: "error" },
                             overrides: [
@@ -3340,10 +3424,16 @@ describe("CLIEngine", () => {
                                     }
                                 }
                             ]
-                        })
+                        }
                     }
-                }).CLIEngine;
-                engine = new CLIEngine({ cwd: root, extensions: ["js", "html"] });
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                engine = new CLIEngine({
+                    cwd: teardown.getPath(),
+                    extensions: ["js", "html"]
+                });
 
                 const { results } = engine.executeOnFiles(["test.md"]);
 
@@ -3357,30 +3447,37 @@ describe("CLIEngine", () => {
                 assert.strictEqual(results[0].messages[2].line, 10);
             });
 
-            it("should throw an error if invalid processor was specified.", () => {
-                CLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
+            it("should throw an error if invalid processor was specified.", async () => {
+
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         ...commonFiles,
-                        ".eslintrc.json": JSON.stringify({
+                        ".eslintrc.json": {
                             plugins: ["markdown", "html"],
                             processor: "markdown/unknown"
-                        })
+                        }
                     }
-                }).CLIEngine;
-                engine = new CLIEngine({ cwd: root });
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                engine = new CLIEngine({
+                    cwd: teardown.getPath()
+                });
 
                 assert.throws(() => {
                     engine.executeOnFiles(["test.md"]);
                 }, /ESLint configuration of processor in '\.eslintrc\.json' is invalid: 'markdown\/unknown' was not found\./u);
             });
 
-            it("should lint HTML blocks as well with multiple processors if 'overrides[].files' is present.", () => {
-                CLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
+            it("should lint HTML blocks as well with multiple processors if 'overrides[].files' is present.", async () => {
+
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         ...commonFiles,
-                        ".eslintrc.json": JSON.stringify({
+                        ".eslintrc.json": {
                             plugins: ["markdown", "html"],
                             rules: { semi: "error" },
                             overrides: [
@@ -3393,10 +3490,15 @@ describe("CLIEngine", () => {
                                     processor: "markdown/.md"
                                 }
                             ]
-                        })
+                        }
                     }
-                }).CLIEngine;
-                engine = new CLIEngine({ cwd: root });
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                engine = new CLIEngine({
+                    cwd: teardown.getPath()
+                });
 
                 const { results } = engine.executeOnFiles(["test.md"]);
 
@@ -3508,27 +3610,35 @@ describe("CLIEngine", () => {
         });
 
         describe("with '--rulesdir' option", () => {
-            it("should use the configured rules which are defined by '--rulesdir' option.", () => {
-                const rootPath = getFixturePath("cli-engine/with-rulesdir");
-                const StubbedCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => rootPath,
-                    files: {
-                        "internal-rules/test.js": `
+
+            const rootPath = getFixturePath("cli-engine/with-rulesdir");
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: rootPath,
+                files: {
+                    "internal-rules/test.js": `
                             module.exports = context => ({
                                 ExpressionStatement(node) {
                                     context.report({ node, message: "ok" })
                                 }
                             })
                         `,
-                        ".eslintrc.json": JSON.stringify({
-                            root: true,
-                            rules: { test: "error" }
-                        }),
-                        "test.js": "console.log('hello')"
-                    }
-                }).CLIEngine;
+                    ".eslintrc.json": {
+                        root: true,
+                        rules: { test: "error" }
+                    },
+                    "test.js": "console.log('hello')"
+                }
+            });
+
+            beforeEach(prepare);
+            afterEach(cleanup);
+
+
+            it("should use the configured rules which are defined by '--rulesdir' option.", () => {
 
-                engine = new StubbedCLIEngine({
+                engine = new CLIEngine({
+                    cwd: getPath(),
                     rulePaths: ["internal-rules"]
                 });
                 const report = engine.executeOnFiles(["test.js"]);
@@ -3542,9 +3652,18 @@ describe("CLIEngine", () => {
         describe("glob pattern '[ab].js'", () => {
             const root = getFixturePath("cli-engine/unmatched-glob");
 
-            it("should match '[ab].js' if existed.", () => {
-                CLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
+            let cleanup;
+
+            beforeEach(() => {
+                cleanup = () => { };
+            });
+
+            afterEach(() => cleanup());
+
+            it("should match '[ab].js' if existed.", async () => {
+
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         "a.js": "",
                         "b.js": "",
@@ -3552,8 +3671,12 @@ describe("CLIEngine", () => {
                         "[ab].js": "",
                         ".eslintrc.yml": "root: true"
                     }
-                }).CLIEngine;
-                engine = new CLIEngine();
+                });
+
+                engine = new CLIEngine({ cwd: teardown.getPath() });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
 
                 const { results } = engine.executeOnFiles(["[ab].js"]);
                 const filenames = results.map(r => path.basename(r.filePath));
@@ -3561,17 +3684,22 @@ describe("CLIEngine", () => {
                 assert.deepStrictEqual(filenames, ["[ab].js"]);
             });
 
-            it("should match 'a.js' and 'b.js' if '[ab].js' didn't existed.", () => {
-                CLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
+            it("should match 'a.js' and 'b.js' if '[ab].js' didn't existed.", async () => {
+
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         "a.js": "",
                         "b.js": "",
                         "ab.js": "",
                         ".eslintrc.yml": "root: true"
                     }
-                }).CLIEngine;
-                engine = new CLIEngine();
+                });
+
+                engine = new CLIEngine({ cwd: teardown.getPath() });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
 
                 const { results } = engine.executeOnFiles(["[ab].js"]);
                 const filenames = results.map(r => path.basename(r.filePath));
@@ -3583,15 +3711,27 @@ describe("CLIEngine", () => {
         describe("with 'noInlineConfig' setting", () => {
             const root = getFixturePath("cli-engine/noInlineConfig");
 
-            it("should warn directive comments if 'noInlineConfig' was given.", () => {
-                CLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
+            let cleanup;
+
+            beforeEach(() => {
+                cleanup = () => { };
+            });
+
+            afterEach(() => cleanup());
+
+            it("should warn directive comments if 'noInlineConfig' was given.", async () => {
+
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         "test.js": "/* globals foo */",
                         ".eslintrc.yml": "noInlineConfig: true"
                     }
-                }).CLIEngine;
-                engine = new CLIEngine();
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                engine = new CLIEngine({ cwd: teardown.getPath() });
 
                 const { results } = engine.executeOnFiles(["test.js"]);
                 const messages = results[0].messages;
@@ -3600,16 +3740,20 @@ describe("CLIEngine", () => {
                 assert.strictEqual(messages[0].message, "'/*globals*/' has no effect because you have 'noInlineConfig' setting in your config (.eslintrc.yml).");
             });
 
-            it("should show the config file what the 'noInlineConfig' came from.", () => {
-                CLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
+            it("should show the config file what the 'noInlineConfig' came from.", async () => {
+
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         "node_modules/eslint-config-foo/index.js": "module.exports = {noInlineConfig: true}",
                         "test.js": "/* globals foo */",
                         ".eslintrc.yml": "extends: foo"
                     }
-                }).CLIEngine;
-                engine = new CLIEngine();
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                engine = new CLIEngine({ cwd: teardown.getPath() });
 
                 const { results } = engine.executeOnFiles(["test.js"]);
                 const messages = results[0].messages;
@@ -3622,15 +3766,26 @@ describe("CLIEngine", () => {
         describe("with 'reportUnusedDisableDirectives' setting", () => {
             const root = getFixturePath("cli-engine/reportUnusedDisableDirectives");
 
-            it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives' was given.", () => {
-                CLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
+            let cleanup;
+
+            beforeEach(() => {
+                cleanup = () => { };
+            });
+
+            afterEach(() => cleanup());
+
+            it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives' was given.", async () => {
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         "test.js": "/* eslint-disable eqeqeq */",
                         ".eslintrc.yml": "reportUnusedDisableDirectives: true"
                     }
-                }).CLIEngine;
-                engine = new CLIEngine();
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                engine = new CLIEngine({ cwd: teardown.getPath() });
 
                 const { results } = engine.executeOnFiles(["test.js"]);
                 const messages = results[0].messages;
@@ -3641,15 +3796,22 @@ describe("CLIEngine", () => {
             });
 
             describe("the runtime option overrides config files.", () => {
-                it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives=off' was given in runtime.", () => {
-                    CLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                        cwd: () => root,
+                it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives=off' was given in runtime.", async () => {
+                    const teardown = createCustomTeardown({
+                        cwd: root,
                         files: {
                             "test.js": "/* eslint-disable eqeqeq */",
                             ".eslintrc.yml": "reportUnusedDisableDirectives: true"
                         }
-                    }).CLIEngine;
-                    engine = new CLIEngine({ reportUnusedDisableDirectives: "off" });
+                    });
+
+                    await teardown.prepare();
+                    cleanup = teardown.cleanup;
+
+                    engine = new CLIEngine({
+                        cwd: teardown.getPath(),
+                        reportUnusedDisableDirectives: "off"
+                    });
 
                     const { results } = engine.executeOnFiles(["test.js"]);
                     const messages = results[0].messages;
@@ -3657,15 +3819,22 @@ describe("CLIEngine", () => {
                     assert.strictEqual(messages.length, 0);
                 });
 
-                it("should warn unused 'eslint-disable' comments as error if 'reportUnusedDisableDirectives=error' was given in runtime.", () => {
-                    CLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                        cwd: () => root,
+                it("should warn unused 'eslint-disable' comments as error if 'reportUnusedDisableDirectives=error' was given in runtime.", async () => {
+                    const teardown = createCustomTeardown({
+                        cwd: root,
                         files: {
                             "test.js": "/* eslint-disable eqeqeq */",
                             ".eslintrc.yml": "reportUnusedDisableDirectives: true"
                         }
-                    }).CLIEngine;
-                    engine = new CLIEngine({ reportUnusedDisableDirectives: "error" });
+                    });
+
+                    await teardown.prepare();
+                    cleanup = teardown.cleanup;
+
+                    engine = new CLIEngine({
+                        cwd: teardown.getPath(),
+                        reportUnusedDisableDirectives: "error"
+                    });
 
                     const { results } = engine.executeOnFiles(["test.js"]);
                     const messages = results[0].messages;
@@ -3680,24 +3849,28 @@ describe("CLIEngine", () => {
         describe("with 'overrides[*].extends' setting on deep locations", () => {
             const root = getFixturePath("cli-engine/deeply-overrides-i-extends");
 
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
+                        overrides: [{ files: ["*test*"], extends: "two" }]
+                    })}`,
+                    "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({
+                        overrides: [{ files: ["*.js"], extends: "three" }]
+                    })}`,
+                    "node_modules/eslint-config-three/index.js": `module.exports = ${JSON.stringify({
+                        rules: { "no-console": "error" }
+                    })}`,
+                    "test.js": "console.log('hello')",
+                    ".eslintrc.yml": "extends: one"
+                }
+            });
+
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("should not throw.", () => {
-                CLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
-                            overrides: [{ files: ["*test*"], extends: "two" }]
-                        })}`,
-                        "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({
-                            overrides: [{ files: ["*.js"], extends: "three" }]
-                        })}`,
-                        "node_modules/eslint-config-three/index.js": `module.exports = ${JSON.stringify({
-                            rules: { "no-console": "error" }
-                        })}`,
-                        "test.js": "console.log('hello')",
-                        ".eslintrc.yml": "extends: one"
-                    }
-                }).CLIEngine;
-                engine = new CLIEngine();
+                engine = new CLIEngine({ cwd: getPath() });
 
                 const { results } = engine.executeOnFiles(["test.js"]);
                 const messages = results[0].messages;
@@ -3710,46 +3883,72 @@ describe("CLIEngine", () => {
         describe("don't ignore the entry directory.", () => {
             const root = getFixturePath("cli-engine/dont-ignore-entry-dir");
 
-            it("'executeOnFiles(\".\")' should not load config files from outside of \".\".", () => {
-                CLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
+            let cleanup;
+
+            beforeEach(() => {
+                cleanup = () => {};
+            });
+
+            afterEach(async () => {
+                await cleanup();
+
+                const configFilePath = path.resolve(root, "../.eslintrc.json");
+
+                if (shell.test("-e", configFilePath)) {
+                    shell.rm(configFilePath);
+                }
+            });
+
+            it("'executeOnFiles(\".\")' should not load config files from outside of \".\".", async () => {
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         "../.eslintrc.json": "BROKEN FILE",
                         ".eslintrc.json": JSON.stringify({ root: true }),
                         "index.js": "console.log(\"hello\")"
                     }
-                }).CLIEngine;
-                engine = new CLIEngine();
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                engine = new CLIEngine({ cwd: teardown.getPath() });
 
                 // Don't throw "failed to load config file" error.
                 engine.executeOnFiles(".");
             });
 
-            it("'executeOnFiles(\".\")' should not ignore '.' even if 'ignorePatterns' contains it.", () => {
-                CLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
+            it("'executeOnFiles(\".\")' should not ignore '.' even if 'ignorePatterns' contains it.", async () => {
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
-                        "../.eslintrc.json": JSON.stringify({ ignorePatterns: ["/dont-ignore-entry-dir"] }),
-                        ".eslintrc.json": JSON.stringify({ root: true }),
+                        "../.eslintrc.json": { ignorePatterns: ["/dont-ignore-entry-dir"] },
+                        ".eslintrc.json": { root: true },
                         "index.js": "console.log(\"hello\")"
                     }
-                }).CLIEngine;
-                engine = new CLIEngine();
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                engine = new CLIEngine({ cwd: teardown.getPath() });
+
 
                 // Don't throw "file not found" error.
                 engine.executeOnFiles(".");
             });
 
-            it("'executeOnFiles(\"subdir\")' should not ignore './subdir' even if 'ignorePatterns' contains it.", () => {
-                CLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
+            it("'executeOnFiles(\"subdir\")' should not ignore './subdir' even if 'ignorePatterns' contains it.", async () => {
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
-                        ".eslintrc.json": JSON.stringify({ ignorePatterns: ["/subdir"] }),
-                        "subdir/.eslintrc.json": JSON.stringify({ root: true }),
+                        ".eslintrc.json": { ignorePatterns: ["/subdir"] },
+                        "subdir/.eslintrc.json": { root: true },
                         "subdir/index.js": "console.log(\"hello\")"
                     }
-                }).CLIEngine;
-                engine = new CLIEngine();
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                engine = new CLIEngine({ cwd: teardown.getPath() });
 
                 // Don't throw "file not found" error.
                 engine.executeOnFiles("subdir");
@@ -4469,7 +4668,9 @@ describe("CLIEngine", () => {
         });
 
         it("should call fs.writeFileSync() for each result with output", () => {
-            const fakeFS = leche.fake(fs),
+            const fakeFS = {
+                    writeFileSync: () => {}
+                },
                 localCLIEngine = proxyquire("../../../lib/cli-engine/cli-engine", {
                     fs: fakeFS
                 }).CLIEngine,
@@ -4486,7 +4687,6 @@ describe("CLIEngine", () => {
                     ]
                 };
 
-            fakeFS.writeFileSync = function() {};
             const spy = sinon.spy(fakeFS, "writeFileSync");
 
             localCLIEngine.outputFixes(report);
@@ -4498,7 +4698,9 @@ describe("CLIEngine", () => {
         });
 
         it("should call fs.writeFileSync() for each result with output and not at all for a result without output", () => {
-            const fakeFS = leche.fake(fs),
+            const fakeFS = {
+                    writeFileSync: () => {}
+                },
                 localCLIEngine = proxyquire("../../../lib/cli-engine/cli-engine", {
                     fs: fakeFS
                 }).CLIEngine,
@@ -4518,7 +4720,6 @@ describe("CLIEngine", () => {
                     ]
                 };
 
-            fakeFS.writeFileSync = function() {};
             const spy = sinon.spy(fakeFS, "writeFileSync");
 
             localCLIEngine.outputFixes(report);
@@ -4555,12 +4756,12 @@ describe("CLIEngine", () => {
 
     describe("resolveFileGlobPatterns", () => {
 
-        leche.withData([
+        [
             [".", ["**/*.{js}"]],
             ["./", ["**/*.{js}"]],
             ["../", ["../**/*.{js}"]],
             ["", []]
-        ], (input, expected) => {
+        ].forEach(([input, expected]) => {
 
             it(`should correctly resolve ${input} to ${expected}`, () => {
                 const engine = new CLIEngine();
@@ -4828,41 +5029,40 @@ describe("CLIEngine", () => {
     describe("with ignorePatterns config", () => {
         const root = getFixturePath("cli-engine/ignore-patterns");
 
-        /** @type {typeof CLIEngine} */
-        let InMemoryCLIEngine;
-
         describe("ignorePatterns can add an ignore pattern ('foo.js').", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.json": JSON.stringify({
-                            ignorePatterns: "foo.js"
-                        }),
-                        "foo.js": "",
-                        "bar.js": "",
-                        "subdir/foo.js": "",
-                        "subdir/bar.js": ""
-                    }
-                }).CLIEngine;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.json": {
+                        ignorePatterns: "foo.js"
+                    },
+                    "foo.js": "",
+                    "bar.js": "",
+                    "subdir/foo.js": "",
+                    "subdir/bar.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("foo.js"), true);
                 assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), true);
             });
 
             it("'isPathIgnored()' should return 'false' for 'bar.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("bar.js"), false);
                 assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), false);
             });
 
             it("'executeOnFiles()' should not verify 'foo.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
                 const filePaths = engine.executeOnFiles("**/*.js")
                     .results
                     .map(r => r.filePath)
@@ -4876,39 +5076,40 @@ describe("CLIEngine", () => {
         });
 
         describe("ignorePatterns can add ignore patterns ('foo.js', '/bar.js').", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.json": JSON.stringify({
-                            ignorePatterns: ["foo.js", "/bar.js"]
-                        }),
-                        "foo.js": "",
-                        "bar.js": "",
-                        "baz.js": "",
-                        "subdir/foo.js": "",
-                        "subdir/bar.js": "",
-                        "subdir/baz.js": ""
-                    }
-                }).CLIEngine;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.json": {
+                        ignorePatterns: ["foo.js", "/bar.js"]
+                    },
+                    "foo.js": "",
+                    "bar.js": "",
+                    "baz.js": "",
+                    "subdir/foo.js": "",
+                    "subdir/bar.js": "",
+                    "subdir/baz.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("foo.js"), true);
                 assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), true);
             });
 
             it("'isPathIgnored()' should return 'true' for '/bar.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("bar.js"), true);
                 assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), false);
             });
 
             it("'executeOnFiles()' should not verify 'foo.js' and '/bar.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
                 const filePaths = engine.executeOnFiles("**/*.js")
                     .results
                     .map(r => r.filePath)
@@ -4923,41 +5124,43 @@ describe("CLIEngine", () => {
         });
 
         describe("ignorePatterns can unignore '/node_modules/foo'.", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.json": JSON.stringify({
-                            ignorePatterns: "!/node_modules/foo"
-                        }),
-                        "node_modules/foo/index.js": "",
-                        "node_modules/foo/.dot.js": "",
-                        "node_modules/bar/index.js": "",
-                        "foo.js": ""
-                    }
-                }).CLIEngine;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.json": {
+                        ignorePatterns: "!/node_modules/foo"
+                    },
+                    "node_modules/foo/index.js": "",
+                    "node_modules/foo/.dot.js": "",
+                    "node_modules/bar/index.js": "",
+                    "foo.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'false' for 'node_modules/foo/index.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("node_modules/foo/index.js"), false);
             });
 
             it("'isPathIgnored()' should return 'true' for 'node_modules/foo/.dot.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("node_modules/foo/.dot.js"), true);
             });
 
             it("'isPathIgnored()' should return 'true' for 'node_modules/bar/index.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("node_modules/bar/index.js"), true);
             });
 
             it("'executeOnFiles()' should verify 'node_modules/foo/index.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
                 const filePaths = engine.executeOnFiles("**/*.js")
                     .results
                     .map(r => r.filePath)
@@ -4971,26 +5174,28 @@ describe("CLIEngine", () => {
         });
 
         describe("ignorePatterns can unignore '.eslintrc.js'.", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.js": `module.exports = ${JSON.stringify({
-                            ignorePatterns: "!.eslintrc.js"
-                        })}`,
-                        "foo.js": ""
-                    }
-                }).CLIEngine;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.js": `module.exports = ${JSON.stringify({
+                        ignorePatterns: "!.eslintrc.js"
+                    })}`,
+                    "foo.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'false' for '.eslintrc.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored(".eslintrc.js"), false);
             });
 
             it("'executeOnFiles()' should verify '.eslintrc.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
                 const filePaths = engine.executeOnFiles("**/*.js")
                     .results
                     .map(r => r.filePath)
@@ -5004,34 +5209,35 @@ describe("CLIEngine", () => {
         });
 
         describe(".eslintignore can re-ignore files that are unignored by ignorePatterns.", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.js": `module.exports = ${JSON.stringify({
-                            ignorePatterns: "!.*"
-                        })}`,
-                        ".eslintignore": ".foo*",
-                        ".foo.js": "",
-                        ".bar.js": ""
-                    }
-                }).CLIEngine;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.js": `module.exports = ${JSON.stringify({
+                        ignorePatterns: "!.*"
+                    })}`,
+                    ".eslintignore": ".foo*",
+                    ".foo.js": "",
+                    ".bar.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'true' for re-ignored '.foo.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored(".foo.js"), true);
             });
 
             it("'isPathIgnored()' should return 'false' for unignored '.bar.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored(".bar.js"), false);
             });
 
             it("'executeOnFiles()' should not verify re-ignored '.foo.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
                 const filePaths = engine.executeOnFiles("**/*.js")
                     .results
                     .map(r => r.filePath)
@@ -5045,34 +5251,36 @@ describe("CLIEngine", () => {
         });
 
         describe(".eslintignore can unignore files that are ignored by ignorePatterns.", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.js": `module.exports = ${JSON.stringify({
-                            ignorePatterns: "*.js"
-                        })}`,
-                        ".eslintignore": "!foo.js",
-                        "foo.js": "",
-                        "bar.js": ""
-                    }
-                }).CLIEngine;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.js": `module.exports = ${JSON.stringify({
+                        ignorePatterns: "*.js"
+                    })}`,
+                    ".eslintignore": "!foo.js",
+                    "foo.js": "",
+                    "bar.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("foo.js"), false);
             });
 
             it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("bar.js"), true);
             });
 
             it("'executeOnFiles()' should verify unignored 'foo.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
                 const filePaths = engine.executeOnFiles("**/*.js")
                     .results
                     .map(r => r.filePath)
@@ -5085,28 +5293,29 @@ describe("CLIEngine", () => {
         });
 
         describe("ignorePatterns in the config file in a child directory affects to only in the directory.", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.json": JSON.stringify({
-                            ignorePatterns: "foo.js"
-                        }),
-                        "subdir/.eslintrc.json": JSON.stringify({
-                            ignorePatterns: "bar.js"
-                        }),
-                        "foo.js": "",
-                        "bar.js": "",
-                        "subdir/foo.js": "",
-                        "subdir/bar.js": "",
-                        "subdir/subsubdir/foo.js": "",
-                        "subdir/subsubdir/bar.js": ""
-                    }
-                }).CLIEngine;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.json": JSON.stringify({
+                        ignorePatterns: "foo.js"
+                    }),
+                    "subdir/.eslintrc.json": JSON.stringify({
+                        ignorePatterns: "bar.js"
+                    }),
+                    "foo.js": "",
+                    "bar.js": "",
+                    "subdir/foo.js": "",
+                    "subdir/bar.js": "",
+                    "subdir/subsubdir/foo.js": "",
+                    "subdir/subsubdir/bar.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("foo.js"), true);
                 assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), true);
@@ -5114,20 +5323,20 @@ describe("CLIEngine", () => {
             });
 
             it("'isPathIgnored()' should return 'true' for 'bar.js' in 'subdir'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), true);
                 assert.strictEqual(engine.isPathIgnored("subdir/subsubdir/bar.js"), true);
             });
 
             it("'isPathIgnored()' should return 'false' for 'bar.js' in the outside of 'subdir'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("bar.js"), false);
             });
 
             it("'executeOnFiles()' should verify 'bar.js' in the outside of 'subdir'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
                 const filePaths = engine.executeOnFiles("**/*.js")
                     .results
                     .map(r => r.filePath)
@@ -5140,36 +5349,37 @@ describe("CLIEngine", () => {
         });
 
         describe("ignorePatterns in the config file in a child directory can unignore the ignored files in the parent directory's config.", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.json": JSON.stringify({
-                            ignorePatterns: "foo.js"
-                        }),
-                        "subdir/.eslintrc.json": JSON.stringify({
-                            ignorePatterns: "!foo.js"
-                        }),
-                        "foo.js": "",
-                        "subdir/foo.js": ""
-                    }
-                }).CLIEngine;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.json": JSON.stringify({
+                        ignorePatterns: "foo.js"
+                    }),
+                    "subdir/.eslintrc.json": JSON.stringify({
+                        ignorePatterns: "!foo.js"
+                    }),
+                    "foo.js": "",
+                    "subdir/foo.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'true' for 'foo.js' in the root directory.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("foo.js"), true);
             });
 
             it("'isPathIgnored()' should return 'false' for 'foo.js' in the child directory.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), false);
             });
 
             it("'executeOnFiles()' should verify 'foo.js' in the child directory.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
                 const filePaths = engine.executeOnFiles("**/*.js")
                     .results
                     .map(r => r.filePath)
@@ -5182,37 +5392,38 @@ describe("CLIEngine", () => {
         });
 
         describe(".eslintignore can unignore files that are ignored by ignorePatterns in the config file in the child directory.", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.json": JSON.stringify({}),
-                        "subdir/.eslintrc.json": JSON.stringify({
-                            ignorePatterns: "*.js"
-                        }),
-                        ".eslintignore": "!foo.js",
-                        "foo.js": "",
-                        "subdir/foo.js": "",
-                        "subdir/bar.js": ""
-                    }
-                }).CLIEngine;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.json": {},
+                    "subdir/.eslintrc.json": {
+                        ignorePatterns: "*.js"
+                    },
+                    ".eslintignore": "!foo.js",
+                    "foo.js": "",
+                    "subdir/foo.js": "",
+                    "subdir/bar.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("foo.js"), false);
                 assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), false);
             });
 
             it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), true);
             });
 
             it("'executeOnFiles()' should verify unignored 'foo.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
                 const filePaths = engine.executeOnFiles("**/*.js")
                     .results
                     .map(r => r.filePath)
@@ -5226,51 +5437,52 @@ describe("CLIEngine", () => {
         });
 
         describe("if the config in a child directory has 'root:true', ignorePatterns in the config file in the parent directory should not be used.", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.json": JSON.stringify({
-                            ignorePatterns: "foo.js"
-                        }),
-                        "subdir/.eslintrc.json": JSON.stringify({
-                            root: true,
-                            ignorePatterns: "bar.js"
-                        }),
-                        "foo.js": "",
-                        "bar.js": "",
-                        "subdir/foo.js": "",
-                        "subdir/bar.js": ""
-                    }
-                }).CLIEngine;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.json": {
+                        ignorePatterns: "foo.js"
+                    },
+                    "subdir/.eslintrc.json": {
+                        root: true,
+                        ignorePatterns: "bar.js"
+                    },
+                    "foo.js": "",
+                    "bar.js": "",
+                    "subdir/foo.js": "",
+                    "subdir/bar.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'true' for 'foo.js' in the root directory.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("foo.js"), true);
             });
 
             it("'isPathIgnored()' should return 'false' for 'bar.js' in the root directory.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("bar.js"), false);
             });
 
             it("'isPathIgnored()' should return 'false' for 'foo.js' in the child directory.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), false);
             });
 
             it("'isPathIgnored()' should return 'true' for 'bar.js' in the child directory.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), true);
             });
 
             it("'executeOnFiles()' should verify 'bar.js' in the root directory and 'foo.js' in the child directory.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
                 const filePaths = engine.executeOnFiles("**/*.js")
                     .results
                     .map(r => r.filePath)
@@ -5284,45 +5496,47 @@ describe("CLIEngine", () => {
         });
 
         describe("even if the config in a child directory has 'root:true', .eslintignore should be used.", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.json": JSON.stringify({}),
-                        "subdir/.eslintrc.json": JSON.stringify({
-                            root: true,
-                            ignorePatterns: "bar.js"
-                        }),
-                        ".eslintignore": "foo.js",
-                        "foo.js": "",
-                        "bar.js": "",
-                        "subdir/foo.js": "",
-                        "subdir/bar.js": ""
-                    }
-                }).CLIEngine;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.json": JSON.stringify({}),
+                    "subdir/.eslintrc.json": JSON.stringify({
+                        root: true,
+                        ignorePatterns: "bar.js"
+                    }),
+                    ".eslintignore": "foo.js",
+                    "foo.js": "",
+                    "bar.js": "",
+                    "subdir/foo.js": "",
+                    "subdir/bar.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("foo.js"), true);
                 assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), true);
             });
 
             it("'isPathIgnored()' should return 'false' for 'bar.js' in the root directory.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("bar.js"), false);
             });
 
             it("'isPathIgnored()' should return 'true' for 'bar.js' in the child directory.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), true);
             });
 
             it("'executeOnFiles()' should verify 'bar.js' in the root directory.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
                 const filePaths = engine.executeOnFiles("**/*.js")
                     .results
                     .map(r => r.filePath)
@@ -5335,36 +5549,37 @@ describe("CLIEngine", () => {
         });
 
         describe("ignorePatterns in the shareable config should be used.", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
-                            ignorePatterns: "foo.js"
-                        })}`,
-                        ".eslintrc.json": JSON.stringify({
-                            extends: "one"
-                        }),
-                        "foo.js": "",
-                        "bar.js": ""
-                    }
-                }).CLIEngine;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
+                        ignorePatterns: "foo.js"
+                    })}`,
+                    ".eslintrc.json": JSON.stringify({
+                        extends: "one"
+                    }),
+                    "foo.js": "",
+                    "bar.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("foo.js"), true);
             });
 
             it("'isPathIgnored()' should return 'false' for 'bar.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("bar.js"), false);
             });
 
             it("'executeOnFiles()' should verify 'bar.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
                 const filePaths = engine.executeOnFiles("**/*.js")
                     .results
                     .map(r => r.filePath)
@@ -5377,36 +5592,39 @@ describe("CLIEngine", () => {
         });
 
         describe("ignorePatterns in the shareable config should be relative to the entry config file.", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
-                            ignorePatterns: "/foo.js"
-                        })}`,
-                        ".eslintrc.json": JSON.stringify({
-                            extends: "one"
-                        }),
-                        "foo.js": "",
-                        "subdir/foo.js": ""
-                    }
-                }).CLIEngine;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
+                        ignorePatterns: "/foo.js"
+                    })}`,
+                    ".eslintrc.json": JSON.stringify({
+                        extends: "one"
+                    }),
+                    "foo.js": "",
+                    "subdir/foo.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
+
             it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("foo.js"), true);
             });
 
             it("'isPathIgnored()' should return 'false' for 'subdir/foo.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), false);
             });
 
             it("'executeOnFiles()' should verify 'subdir/foo.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
                 const filePaths = engine.executeOnFiles("**/*.js")
                     .results
                     .map(r => r.filePath)
@@ -5419,37 +5637,39 @@ describe("CLIEngine", () => {
         });
 
         describe("ignorePatterns in a config file can unignore the files which are ignored by ignorePatterns in the shareable config.", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
-                            ignorePatterns: "*.js"
-                        })}`,
-                        ".eslintrc.json": JSON.stringify({
-                            extends: "one",
-                            ignorePatterns: "!bar.js"
-                        }),
-                        "foo.js": "",
-                        "bar.js": ""
-                    }
-                }).CLIEngine;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
+                        ignorePatterns: "*.js"
+                    })}`,
+                    ".eslintrc.json": JSON.stringify({
+                        extends: "one",
+                        ignorePatterns: "!bar.js"
+                    }),
+                    "foo.js": "",
+                    "bar.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("foo.js"), true);
             });
 
             it("'isPathIgnored()' should return 'false' for 'bar.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assert.strictEqual(engine.isPathIgnored("bar.js"), false);
             });
 
             it("'executeOnFiles()' should verify 'bar.js'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
                 const filePaths = engine.executeOnFiles("**/*.js")
                     .results
                     .map(r => r.filePath)
@@ -5462,26 +5682,27 @@ describe("CLIEngine", () => {
         });
 
         describe("ignorePatterns in a config file should not be used if --no-ignore option was given.", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.json": JSON.stringify({
-                            ignorePatterns: "*.js"
-                        }),
-                        "foo.js": ""
-                    }
-                }).CLIEngine;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.json": JSON.stringify({
+                        ignorePatterns: "*.js"
+                    }),
+                    "foo.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'false' for 'foo.js'.", () => {
-                const engine = new InMemoryCLIEngine({ ignore: false });
+                const engine = new CLIEngine({ cwd: getPath(), ignore: false });
 
                 assert.strictEqual(engine.isPathIgnored("foo.js"), false);
             });
 
             it("'executeOnFiles()' should verify 'foo.js'.", () => {
-                const engine = new InMemoryCLIEngine({ ignore: false });
+                const engine = new CLIEngine({ cwd: getPath(), ignore: false });
                 const filePaths = engine.executeOnFiles("**/*.js")
                     .results
                     .map(r => r.filePath)
@@ -5494,26 +5715,28 @@ describe("CLIEngine", () => {
         });
 
         describe("ignorePatterns in overrides section is not allowed.", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.js": `module.exports = ${JSON.stringify({
-                            overrides: [
-                                {
-                                    files: "*.js",
-                                    ignorePatterns: "foo.js"
-                                }
-                            ]
-                        })}`,
-                        "foo.js": ""
-                    }
-                }).CLIEngine;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.js": `module.exports = ${JSON.stringify({
+                        overrides: [
+                            {
+                                files: "*.js",
+                                ignorePatterns: "foo.js"
+                            }
+                        ]
+                    })}`,
+                    "foo.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("should throw a configuration error.", () => {
                 assert.throws(() => {
-                    const engine = new InMemoryCLIEngine();
+                    const engine = new CLIEngine({ cwd: getPath() });
 
                     engine.executeOnFiles("*.js");
                 }, "Unexpected top-level property \"overrides[0].ignorePatterns\"");
@@ -5524,37 +5747,38 @@ describe("CLIEngine", () => {
 
     describe("'overrides[].files' adds lint targets", () => {
         const root = getFixturePath("cli-engine/additional-lint-targets");
-        let InMemoryCLIEngine;
 
         describe("if { files: 'foo/*.txt', excludedFiles: '**/ignore.txt' } is present,", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.json": JSON.stringify({
-                            overrides: [
-                                {
-                                    files: "foo/*.txt",
-                                    excludedFiles: "**/ignore.txt"
-                                }
-                            ]
-                        }),
-                        "foo/nested/test.txt": "",
-                        "foo/test.js": "",
-                        "foo/test.txt": "",
-                        "foo/ignore.txt": "",
-                        "bar/test.js": "",
-                        "bar/test.txt": "",
-                        "bar/ignore.txt": "",
-                        "test.js": "",
-                        "test.txt": "",
-                        "ignore.txt": ""
-                    }
-                }).CLIEngine;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.json": JSON.stringify({
+                        overrides: [
+                            {
+                                files: "foo/*.txt",
+                                excludedFiles: "**/ignore.txt"
+                            }
+                        ]
+                    }),
+                    "foo/nested/test.txt": "",
+                    "foo/test.js": "",
+                    "foo/test.txt": "",
+                    "foo/ignore.txt": "",
+                    "bar/test.js": "",
+                    "bar/test.txt": "",
+                    "bar/ignore.txt": "",
+                    "test.js": "",
+                    "test.txt": "",
+                    "ignore.txt": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'executeOnFiles()' with a directory path should contain 'foo/test.txt'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
                 const filePaths = engine.executeOnFiles(".")
                     .results
                     .map(r => r.filePath)
@@ -5569,7 +5793,7 @@ describe("CLIEngine", () => {
             });
 
             it("'executeOnFiles()' with a glob pattern '*.js' should not contain 'foo/test.txt'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
                 const filePaths = engine.executeOnFiles("**/*.js")
                     .results
                     .map(r => r.filePath)
@@ -5584,30 +5808,32 @@ describe("CLIEngine", () => {
         });
 
         describe("if { files: 'foo/**/*.txt' } is present,", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.json": JSON.stringify({
-                            overrides: [
-                                {
-                                    files: "foo/**/*.txt"
-                                }
-                            ]
-                        }),
-                        "foo/nested/test.txt": "",
-                        "foo/test.js": "",
-                        "foo/test.txt": "",
-                        "bar/test.js": "",
-                        "bar/test.txt": "",
-                        "test.js": "",
-                        "test.txt": ""
-                    }
-                }).CLIEngine;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.json": JSON.stringify({
+                        overrides: [
+                            {
+                                files: "foo/**/*.txt"
+                            }
+                        ]
+                    }),
+                    "foo/nested/test.txt": "",
+                    "foo/test.js": "",
+                    "foo/test.txt": "",
+                    "bar/test.js": "",
+                    "bar/test.txt": "",
+                    "test.js": "",
+                    "test.txt": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'executeOnFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
                 const filePaths = engine.executeOnFiles(".")
                     .results
                     .map(r => r.filePath)
@@ -5624,30 +5850,31 @@ describe("CLIEngine", () => {
         });
 
         describe("if { files: 'foo/**/*' } is present,", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.json": JSON.stringify({
-                            overrides: [
-                                {
-                                    files: "foo/**/*"
-                                }
-                            ]
-                        }),
-                        "foo/nested/test.txt": "",
-                        "foo/test.js": "",
-                        "foo/test.txt": "",
-                        "bar/test.js": "",
-                        "bar/test.txt": "",
-                        "test.js": "",
-                        "test.txt": ""
-                    }
-                }).CLIEngine;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.json": JSON.stringify({
+                        overrides: [
+                            {
+                                files: "foo/**/*"
+                            }
+                        ]
+                    }),
+                    "foo/nested/test.txt": "",
+                    "foo/test.js": "",
+                    "foo/test.txt": "",
+                    "bar/test.js": "",
+                    "bar/test.txt": "",
+                    "test.js": "",
+                    "test.txt": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'executeOnFiles()' with a directory path should NOT contain 'foo/test.txt' and 'foo/nested/test.txt'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
                 const filePaths = engine.executeOnFiles(".")
                     .results
                     .map(r => r.filePath)
@@ -5662,33 +5889,34 @@ describe("CLIEngine", () => {
         });
 
         describe("if { files: 'foo/**/*.txt' } is present in a shareable config,", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-config-foo/index.js": `module.exports = ${JSON.stringify({
-                            overrides: [
-                                {
-                                    files: "foo/**/*.txt"
-                                }
-                            ]
-                        })}`,
-                        ".eslintrc.json": JSON.stringify({
-                            extends: "foo"
-                        }),
-                        "foo/nested/test.txt": "",
-                        "foo/test.js": "",
-                        "foo/test.txt": "",
-                        "bar/test.js": "",
-                        "bar/test.txt": "",
-                        "test.js": "",
-                        "test.txt": ""
-                    }
-                }).CLIEngine;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    "node_modules/eslint-config-foo/index.js": `module.exports = ${JSON.stringify({
+                        overrides: [
+                            {
+                                files: "foo/**/*.txt"
+                            }
+                        ]
+                    })}`,
+                    ".eslintrc.json": JSON.stringify({
+                        extends: "foo"
+                    }),
+                    "foo/nested/test.txt": "",
+                    "foo/test.js": "",
+                    "foo/test.txt": "",
+                    "bar/test.js": "",
+                    "bar/test.txt": "",
+                    "test.js": "",
+                    "test.txt": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'executeOnFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
                 const filePaths = engine.executeOnFiles(".")
                     .results
                     .map(r => r.filePath)
@@ -5705,35 +5933,36 @@ describe("CLIEngine", () => {
         });
 
         describe("if { files: 'foo/**/*.txt' } is present in a plugin config,", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-plugin-foo/index.js": `exports.configs = ${JSON.stringify({
-                            bar: {
-                                overrides: [
-                                    {
-                                        files: "foo/**/*.txt"
-                                    }
-                                ]
-                            }
-                        })}`,
-                        ".eslintrc.json": JSON.stringify({
-                            extends: "plugin:foo/bar"
-                        }),
-                        "foo/nested/test.txt": "",
-                        "foo/test.js": "",
-                        "foo/test.txt": "",
-                        "bar/test.js": "",
-                        "bar/test.txt": "",
-                        "test.js": "",
-                        "test.txt": ""
-                    }
-                }).CLIEngine;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    "node_modules/eslint-plugin-foo/index.js": `exports.configs = ${JSON.stringify({
+                        bar: {
+                            overrides: [
+                                {
+                                    files: "foo/**/*.txt"
+                                }
+                            ]
+                        }
+                    })}`,
+                    ".eslintrc.json": JSON.stringify({
+                        extends: "plugin:foo/bar"
+                    }),
+                    "foo/nested/test.txt": "",
+                    "foo/test.js": "",
+                    "foo/test.txt": "",
+                    "bar/test.js": "",
+                    "bar/test.txt": "",
+                    "test.js": "",
+                    "test.txt": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'executeOnFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", () => {
-                const engine = new InMemoryCLIEngine();
+                const engine = new CLIEngine({ cwd: getPath() });
                 const filePaths = engine.executeOnFiles(".")
                     .results
                     .map(r => r.filePath)
@@ -5753,34 +5982,32 @@ describe("CLIEngine", () => {
     describe("'ignorePatterns', 'overrides[].files', and 'overrides[].excludedFiles' of the configuration that the '--config' option provided should be resolved from CWD.", () => {
         const root = getFixturePath("cli-engine/config-and-overrides-files");
 
-        /** @type {CLIEngine} */
-        let InMemoryCLIEngine;
-
         describe("if { files: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/myconf/.eslintrc.json": JSON.stringify({
-                            overrides: [
-                                {
-                                    files: "foo/*.js",
-                                    rules: {
-                                        eqeqeq: "error"
-                                    }
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    "node_modules/myconf/.eslintrc.json": JSON.stringify({
+                        overrides: [
+                            {
+                                files: "foo/*.js",
+                                rules: {
+                                    eqeqeq: "error"
                                 }
-                            ]
-                        }),
-                        "node_modules/myconf/foo/test.js": "a == b",
-                        "foo/test.js": "a == b"
-                    }
-                }).CLIEngine;
+                            }
+                        ]
+                    }),
+                    "node_modules/myconf/foo/test.js": "a == b",
+                    "foo/test.js": "a == b"
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'executeOnFiles()' with 'foo/test.js' should use the override entry.", () => {
-                const engine = new InMemoryCLIEngine({
+                const engine = new CLIEngine({
                     configFile: "node_modules/myconf/.eslintrc.json",
-                    cwd: root,
+                    cwd: getPath(),
                     ignore: false,
                     useEslintrc: false
                 });
@@ -5813,9 +6040,9 @@ describe("CLIEngine", () => {
             });
 
             it("'executeOnFiles()' with 'node_modules/myconf/foo/test.js' should NOT use the override entry.", () => {
-                const engine = new InMemoryCLIEngine({
+                const engine = new CLIEngine({
                     configFile: "node_modules/myconf/.eslintrc.json",
-                    cwd: root,
+                    cwd: getPath(),
                     ignore: false,
                     useEslintrc: false
                 });
@@ -5836,31 +6063,32 @@ describe("CLIEngine", () => {
         });
 
         describe("if { files: '*', excludedFiles: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/myconf/.eslintrc.json": JSON.stringify({
-                            overrides: [
-                                {
-                                    files: "*",
-                                    excludedFiles: "foo/*.js",
-                                    rules: {
-                                        eqeqeq: "error"
-                                    }
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    "node_modules/myconf/.eslintrc.json": JSON.stringify({
+                        overrides: [
+                            {
+                                files: "*",
+                                excludedFiles: "foo/*.js",
+                                rules: {
+                                    eqeqeq: "error"
                                 }
-                            ]
-                        }),
-                        "node_modules/myconf/foo/test.js": "a == b",
-                        "foo/test.js": "a == b"
-                    }
-                }).CLIEngine;
+                            }
+                        ]
+                    }),
+                    "node_modules/myconf/foo/test.js": "a == b",
+                    "foo/test.js": "a == b"
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'executeOnFiles()' with 'foo/test.js' should NOT use the override entry.", () => {
-                const engine = new InMemoryCLIEngine({
+                const engine = new CLIEngine({
                     configFile: "node_modules/myconf/.eslintrc.json",
-                    cwd: root,
+                    cwd: getPath(),
                     ignore: false,
                     useEslintrc: false
                 });
@@ -5880,9 +6108,9 @@ describe("CLIEngine", () => {
             });
 
             it("'executeOnFiles()' with 'node_modules/myconf/foo/test.js' should use the override entry.", () => {
-                const engine = new InMemoryCLIEngine({
+                const engine = new CLIEngine({
                     configFile: "node_modules/myconf/.eslintrc.json",
-                    cwd: root,
+                    cwd: getPath(),
                     ignore: false,
                     useEslintrc: false
                 });
@@ -5916,26 +6144,27 @@ describe("CLIEngine", () => {
         });
 
         describe("if { ignorePatterns: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/myconf/.eslintrc.json": JSON.stringify({
-                            ignorePatterns: ["!/node_modules/myconf", "foo/*.js"],
-                            rules: {
-                                eqeqeq: "error"
-                            }
-                        }),
-                        "node_modules/myconf/foo/test.js": "a == b",
-                        "foo/test.js": "a == b"
-                    }
-                }).CLIEngine;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    "node_modules/myconf/.eslintrc.json": JSON.stringify({
+                        ignorePatterns: ["!/node_modules/myconf", "foo/*.js"],
+                        rules: {
+                            eqeqeq: "error"
+                        }
+                    }),
+                    "node_modules/myconf/foo/test.js": "a == b",
+                    "foo/test.js": "a == b"
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'executeOnFiles()' with '**/*.js' should iterate 'node_modules/myconf/foo/test.js' but not 'foo/test.js'.", () => {
-                const engine = new InMemoryCLIEngine({
+                const engine = new CLIEngine({
                     configFile: "node_modules/myconf/.eslintrc.json",
-                    cwd: root,
+                    cwd: getPath(),
                     useEslintrc: false
                 });
                 const files = engine.executeOnFiles("**/*.js")
@@ -5952,14 +6181,7 @@ describe("CLIEngine", () => {
 
     describe("plugin conflicts", () => {
         let uid = 0;
-        let root = "";
-
-        beforeEach(() => {
-            root = getFixturePath(`cli-engine/plugin-conflicts-${++uid}`);
-        });
-
-        /** @type {typeof CLIEngine} */
-        let InMemoryCLIEngine;
+        const root = getFixturePath("cli-engine/plugin-conflicts-");
 
         /**
          * Verify thrown errors.
@@ -5981,110 +6203,115 @@ describe("CLIEngine", () => {
         }
 
         describe("between a config file and linear extendees.", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-plugin-foo/index.js": "",
-                        "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": "",
-                        "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
-                            extends: ["two"],
-                            plugins: ["foo"]
-                        })}`,
-                        "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": "",
-                        "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({
-                            plugins: ["foo"]
-                        })}`,
-                        ".eslintrc.json": JSON.stringify({
-                            extends: ["one"],
-                            plugins: ["foo"]
-                        }),
-                        "test.js": ""
-                    }
-                }).CLIEngine;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: `${root}${++uid}`,
+                files: {
+                    "node_modules/eslint-plugin-foo/index.js": "",
+                    "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": "",
+                    "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
+                        extends: ["two"],
+                        plugins: ["foo"]
+                    })}`,
+                    "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": "",
+                    "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({
+                        plugins: ["foo"]
+                    })}`,
+                    ".eslintrc.json": JSON.stringify({
+                        extends: ["one"],
+                        plugins: ["foo"]
+                    }),
+                    "test.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file.)", () => {
-                const engine = new InMemoryCLIEngine({ cwd: root });
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 engine.executeOnFiles("test.js");
             });
         });
 
         describe("between a config file and same-depth extendees.", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-plugin-foo/index.js": "",
-                        "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": "",
-                        "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
-                            plugins: ["foo"]
-                        })}`,
-                        "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": "",
-                        "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({
-                            plugins: ["foo"]
-                        })}`,
-                        ".eslintrc.json": JSON.stringify({
-                            extends: ["one", "two"],
-                            plugins: ["foo"]
-                        }),
-                        "test.js": ""
-                    }
-                }).CLIEngine;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: `${root}${++uid}`,
+                files: {
+                    "node_modules/eslint-plugin-foo/index.js": "",
+                    "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": "",
+                    "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
+                        plugins: ["foo"]
+                    })}`,
+                    "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": "",
+                    "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({
+                        plugins: ["foo"]
+                    })}`,
+                    ".eslintrc.json": JSON.stringify({
+                        extends: ["one", "two"],
+                        plugins: ["foo"]
+                    }),
+                    "test.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file.)", () => {
-                const engine = new InMemoryCLIEngine({ cwd: root });
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 engine.executeOnFiles("test.js");
             });
         });
 
         describe("between two config files in different directories, with single node_modules.", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-plugin-foo/index.js": "",
-                        ".eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        "subdir/.eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        "subdir/test.js": ""
-                    }
-                }).CLIEngine;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: `${root}${++uid}`,
+                files: {
+                    "node_modules/eslint-plugin-foo/index.js": "",
+                    ".eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    "subdir/.eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    "subdir/test.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files, but node_modules directory is unique.)", () => {
-                const engine = new InMemoryCLIEngine({ cwd: root });
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 engine.executeOnFiles("subdir/test.js");
             });
         });
 
         describe("between two config files in different directories, with multiple node_modules.", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-plugin-foo/index.js": "",
-                        ".eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        "subdir/node_modules/eslint-plugin-foo/index.js": "",
-                        "subdir/.eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        "subdir/test.js": ""
-                    }
-                }).CLIEngine;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: `${root}${++uid}`,
+                files: {
+                    "node_modules/eslint-plugin-foo/index.js": "",
+                    ".eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    "subdir/node_modules/eslint-plugin-foo/index.js": "",
+                    "subdir/.eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    "subdir/test.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'executeOnFiles()' should throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files.)", () => {
-                const engine = new InMemoryCLIEngine({ cwd: root });
+                const engine = new CLIEngine({ cwd: getPath() });
 
                 assertThrows(
                     () => engine.executeOnFiles("subdir/test.js"),
@@ -6095,11 +6322,11 @@ describe("CLIEngine", () => {
                             pluginId: "foo",
                             plugins: [
                                 {
-                                    filePath: path.join(root, "subdir/node_modules/eslint-plugin-foo/index.js"),
+                                    filePath: path.join(getPath(), "subdir/node_modules/eslint-plugin-foo/index.js"),
                                     importerName: `subdir${path.sep}.eslintrc.json`
                                 },
                                 {
-                                    filePath: path.join(root, "node_modules/eslint-plugin-foo/index.js"),
+                                    filePath: path.join(getPath(), "node_modules/eslint-plugin-foo/index.js"),
                                     importerName: ".eslintrc.json"
                                 }
                             ]
@@ -6110,25 +6337,26 @@ describe("CLIEngine", () => {
         });
 
         describe("between '--config' option and a regular config file, with single node_modules.", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-plugin-foo/index.js": "",
-                        "node_modules/mine/.eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        ".eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        "test.js": ""
-                    }
-                }).CLIEngine;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: `${root}${++uid}`,
+                files: {
+                    "node_modules/eslint-plugin-foo/index.js": "",
+                    "node_modules/mine/.eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    ".eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    "test.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files, but node_modules directory is unique.)", () => {
-                const engine = new InMemoryCLIEngine({
-                    cwd: root,
+                const engine = new CLIEngine({
+                    cwd: getPath(),
                     configFile: "node_modules/mine/.eslintrc.json"
                 });
 
@@ -6137,26 +6365,27 @@ describe("CLIEngine", () => {
         });
 
         describe("between '--config' option and a regular config file, with multiple node_modules.", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-plugin-foo/index.js": "",
-                        "node_modules/mine/node_modules/eslint-plugin-foo/index.js": "",
-                        "node_modules/mine/.eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        ".eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        "test.js": ""
-                    }
-                }).CLIEngine;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: `${root}${++uid}`,
+                files: {
+                    "node_modules/eslint-plugin-foo/index.js": "",
+                    "node_modules/mine/node_modules/eslint-plugin-foo/index.js": "",
+                    "node_modules/mine/.eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    ".eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    "test.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'executeOnFiles()' should throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files.)", () => {
-                const engine = new InMemoryCLIEngine({
-                    cwd: root,
+                const engine = new CLIEngine({
+                    cwd: getPath(),
                     configFile: "node_modules/mine/.eslintrc.json"
                 });
 
@@ -6169,11 +6398,11 @@ describe("CLIEngine", () => {
                             pluginId: "foo",
                             plugins: [
                                 {
-                                    filePath: path.join(root, "node_modules/mine/node_modules/eslint-plugin-foo/index.js"),
+                                    filePath: path.join(getPath(), "node_modules/mine/node_modules/eslint-plugin-foo/index.js"),
                                     importerName: "--config"
                                 },
                                 {
-                                    filePath: path.join(root, "node_modules/eslint-plugin-foo/index.js"),
+                                    filePath: path.join(getPath(), "node_modules/eslint-plugin-foo/index.js"),
                                     importerName: ".eslintrc.json"
                                 }
                             ]
@@ -6184,22 +6413,23 @@ describe("CLIEngine", () => {
         });
 
         describe("between '--plugin' option and a regular config file, with single node_modules.", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-plugin-foo/index.js": "",
-                        "subdir/.eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        "subdir/test.js": ""
-                    }
-                }).CLIEngine;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: `${root}${++uid}`,
+                files: {
+                    "node_modules/eslint-plugin-foo/index.js": "",
+                    "subdir/.eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    "subdir/test.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from both CWD and the base directory of the entry config file, but node_modules directory is unique.)", () => {
-                const engine = new InMemoryCLIEngine({
-                    cwd: root,
+                const engine = new CLIEngine({
+                    cwd: getPath(),
                     plugins: ["foo"]
                 });
 
@@ -6208,23 +6438,24 @@ describe("CLIEngine", () => {
         });
 
         describe("between '--plugin' option and a regular config file, with multiple node_modules.", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-plugin-foo/index.js": "",
-                        "subdir/node_modules/eslint-plugin-foo/index.js": "",
-                        "subdir/.eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        "subdir/test.js": ""
-                    }
-                }).CLIEngine;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: `${root}${++uid}`,
+                files: {
+                    "node_modules/eslint-plugin-foo/index.js": "",
+                    "subdir/node_modules/eslint-plugin-foo/index.js": "",
+                    "subdir/.eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    "subdir/test.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'executeOnFiles()' should throw plugin-conflict error. (Load the plugin from both CWD and the base directory of the entry config file.)", () => {
-                const engine = new InMemoryCLIEngine({
-                    cwd: root,
+                const engine = new CLIEngine({
+                    cwd: getPath(),
                     plugins: ["foo"]
                 });
 
@@ -6237,11 +6468,11 @@ describe("CLIEngine", () => {
                             pluginId: "foo",
                             plugins: [
                                 {
-                                    filePath: path.join(root, "node_modules/eslint-plugin-foo/index.js"),
+                                    filePath: path.join(getPath(), "node_modules/eslint-plugin-foo/index.js"),
                                     importerName: "CLIOptions"
                                 },
                                 {
-                                    filePath: path.join(root, "subdir/node_modules/eslint-plugin-foo/index.js"),
+                                    filePath: path.join(getPath(), "subdir/node_modules/eslint-plugin-foo/index.js"),
                                     importerName: `subdir${path.sep}.eslintrc.json`
                                 }
                             ]
@@ -6252,27 +6483,28 @@ describe("CLIEngine", () => {
         });
 
         describe("'--resolve-plugins-relative-to' option overrides the location that ESLint load plugins from.", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-plugin-foo/index.js": "",
-                        ".eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        "subdir/node_modules/eslint-plugin-foo/index.js": "",
-                        "subdir/.eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        "subdir/test.js": ""
-                    }
-                }).CLIEngine;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: `${root}${++uid}`,
+                files: {
+                    "node_modules/eslint-plugin-foo/index.js": "",
+                    ".eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    "subdir/node_modules/eslint-plugin-foo/index.js": "",
+                    "subdir/.eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    "subdir/test.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from '--resolve-plugins-relative-to'.)", () => {
-                const engine = new InMemoryCLIEngine({
-                    cwd: root,
-                    resolvePluginsRelativeTo: root
+                const engine = new CLIEngine({
+                    cwd: getPath(),
+                    resolvePluginsRelativeTo: getPath()
                 });
 
                 engine.executeOnFiles("subdir/test.js");
@@ -6280,26 +6512,27 @@ describe("CLIEngine", () => {
         });
 
         describe("between two config files with different target files.", () => {
-            beforeEach(() => {
-                InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "one/node_modules/eslint-plugin-foo/index.js": "",
-                        "one/.eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        "one/test.js": "",
-                        "two/node_modules/eslint-plugin-foo/index.js": "",
-                        "two/.eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        "two/test.js": ""
-                    }
-                }).CLIEngine;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: `${root}${++uid}`,
+                files: {
+                    "one/node_modules/eslint-plugin-foo/index.js": "",
+                    "one/.eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    "one/test.js": "",
+                    "two/node_modules/eslint-plugin-foo/index.js": "",
+                    "two/.eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    "two/test.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file for each target file. Not related to each other.)", () => {
-                const engine = new InMemoryCLIEngine({ cwd: root });
+                const engine = new CLIEngine({ cwd: getPath() });
                 const { results } = engine.executeOnFiles("*/test.js");
 
                 assert.strictEqual(results.length, 2);
diff --git a/eslint/tests/lib/cli-engine/config-array-factory.js b/eslint/tests/lib/cli-engine/config-array-factory.js
deleted file mode 100644 (file)
index 3b979c1..0000000
+++ /dev/null
@@ -1,2417 +0,0 @@
-/**
- * @fileoverview Tests for ConfigArrayFactory class.
- * @author Toru Nagashima <https://github.com/mysticatea>
- */
-"use strict";
-
-const os = require("os");
-const path = require("path");
-const { assert } = require("chai");
-const { spy } = require("sinon");
-const { ConfigArray } = require("../../../lib/cli-engine/config-array");
-const { OverrideTester } = require("../../../lib/cli-engine/config-array");
-const { createContext } = require("../../../lib/cli-engine/config-array-factory");
-const { defineConfigArrayFactoryWithInMemoryFileSystem } = require("../../_utils");
-
-const tempDir = path.join(os.tmpdir(), "eslint/config-array-factory");
-
-// For VSCode intellisense.
-/** @typedef {InstanceType<ReturnType<defineConfigArrayFactoryWithInMemoryFileSystem>["ConfigArrayFactory"]>} ConfigArrayFactory */
-
-/**
- * Assert a config array element.
- * @param {Object} actual The actual value.
- * @param {Object} providedExpected The expected value.
- * @returns {void}
- */
-function assertConfigArrayElement(actual, providedExpected) {
-    const expected = {
-        name: "",
-        filePath: "",
-        criteria: null,
-        env: void 0,
-        globals: void 0,
-        ignorePattern: void 0,
-        noInlineConfig: void 0,
-        parser: void 0,
-        parserOptions: void 0,
-        plugins: void 0,
-        processor: void 0,
-        reportUnusedDisableDirectives: void 0,
-        root: void 0,
-        rules: void 0,
-        settings: void 0,
-        type: "config",
-        ...providedExpected
-    };
-
-    assert.deepStrictEqual(actual, expected);
-}
-
-/**
- * Assert a config array element.
- * @param {Object} actual The actual value.
- * @param {Object} providedExpected The expected value.
- * @returns {void}
- */
-function assertConfig(actual, providedExpected) {
-    const expected = {
-        env: {},
-        globals: {},
-        ignorePatterns: [],
-        noInlineConfig: void 0,
-        parser: null,
-        parserOptions: {},
-        plugins: [],
-        reportUnusedDisableDirectives: void 0,
-        rules: {},
-        settings: {},
-        ...providedExpected
-    };
-
-    assert.deepStrictEqual(actual, expected);
-}
-
-/**
- * Assert a plugin definition.
- * @param {Object} actual The actual value.
- * @param {Object} providedExpected The expected value.
- * @returns {void}
- */
-function assertPluginDefinition(actual, providedExpected) {
-    const expected = {
-        configs: {},
-        environments: {},
-        processors: {},
-        rules: {},
-        ...providedExpected
-    };
-
-    assert.deepStrictEqual(actual, expected);
-}
-
-describe("ConfigArrayFactory", () => {
-    describe("'create(configData, options)' method should normalize the config data.", () => {
-        const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-            cwd: () => tempDir
-        });
-
-        /** @type {ConfigArrayFactory} */
-        let factory;
-
-        beforeEach(() => {
-            factory = new ConfigArrayFactory();
-        });
-
-        it("should return an empty config array if 'configData' is null.", () => {
-            assert.strictEqual(factory.create(null).length, 0);
-        });
-
-        it("should throw an error if the config data had invalid properties,", () => {
-            assert.throws(() => {
-                factory.create({ files: true });
-            }, /Unexpected top-level property "files"/u);
-        });
-
-        it("should call '_normalizeConfigData(configData, ctx)' with given arguments.", () => {
-            const configData = {};
-            const basePath = tempDir;
-            const filePath = __filename;
-            const name = "example";
-            const normalizeConfigData = spy(factory, "_normalizeConfigData");
-
-            factory.create(configData, { basePath, filePath, name });
-
-            assert.strictEqual(normalizeConfigData.callCount, 1);
-            assert.deepStrictEqual(normalizeConfigData.args[0], [
-                configData,
-                createContext({ cwd: tempDir }, void 0, name, filePath, basePath)
-            ]);
-        });
-
-        it("should return a config array that contains the yielded elements from '_normalizeConfigData(configData, ctx)'.", () => {
-            const elements = [{}, {}];
-
-            factory._normalizeConfigData = () => elements; // eslint-disable-line no-underscore-dangle
-
-            const configArray = factory.create({});
-
-            assert.strictEqual(configArray.length, 2);
-            assert.strictEqual(configArray[0], elements[0]);
-            assert.strictEqual(configArray[1], elements[1]);
-        });
-    });
-
-    describe("'loadFile(filePath, options)' method should load a config file.", () => {
-        const basicFiles = {
-            "js/.eslintrc.js": "exports.settings = { name: 'js/.eslintrc.js' }",
-            "cjs/.eslintrc.cjs": "exports.settings = { name: 'cjs/.eslintrc.cjs' }",
-            "json/.eslintrc.json": "{ \"settings\": { \"name\": \"json/.eslintrc.json\" } }",
-            "legacy-json/.eslintrc": "{ \"settings\": { \"name\": \"legacy-json/.eslintrc\" } }",
-            "legacy-yml/.eslintrc": "settings:\n  name: legacy-yml/.eslintrc",
-            "package-json/package.json": "{ \"eslintConfig\": { \"settings\": { \"name\": \"package-json/package.json\" } } }",
-            "yml/.eslintrc.yml": "settings:\n  name: yml/.eslintrc.yml",
-            "yaml/.eslintrc.yaml": "settings:\n  name: yaml/.eslintrc.yaml"
-        };
-        const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-            cwd: () => tempDir,
-            files: {
-                ...basicFiles,
-                "invalid-property.json": "{ \"files\": \"*.js\" }",
-                "package-json-no-config/package.json": "{ \"name\": \"foo\" }"
-            }
-        });
-
-        /** @type {ConfigArrayFactory} */
-        let factory;
-
-        beforeEach(() => {
-            factory = new ConfigArrayFactory();
-        });
-
-        it("should throw an error if 'filePath' is null.", () => {
-            assert.throws(() => factory.loadFile(null));
-        });
-
-        it("should throw an error if 'filePath' doesn't exist.", () => {
-            assert.throws(() => {
-                factory.loadFile("non-exist");
-            }, /Cannot read config file:.*non-exist/su);
-        });
-
-        it("should throw an error if 'filePath' was 'package.json' and it doesn't have 'eslintConfig' field.", () => {
-            assert.throws(() => {
-                factory.loadFile("package-json-no-config/package.json");
-            }, /Cannot read config file:.*package.json/su);
-        });
-
-        it("should throw an error if the config data had invalid properties,", () => {
-            assert.throws(() => {
-                factory.loadFile("invalid-property.json");
-            }, /Unexpected top-level property "files"/u);
-        });
-
-        for (const filePath of Object.keys(basicFiles)) {
-            it(`should load '${filePath}' then return a config array what contains that file content.`, () => { // eslint-disable-line no-loop-func
-                const configArray = factory.loadFile(filePath);
-
-                assert.strictEqual(configArray.length, 1);
-                assertConfigArrayElement(configArray[0], {
-                    filePath: path.resolve(tempDir, filePath),
-                    name: path.relative(tempDir, path.resolve(tempDir, filePath)),
-                    settings: { name: filePath }
-                });
-            });
-        }
-
-        it("should call '_normalizeConfigData(configData, ctx)' with the loaded config data and given options.", () => {
-            const basePath = tempDir;
-            const filePath = "js/.eslintrc.js";
-            const name = "example";
-            const normalizeConfigData = spy(factory, "_normalizeConfigData");
-
-            factory.loadFile(filePath, { basePath, name });
-
-            assert.strictEqual(normalizeConfigData.callCount, 1);
-            assert.deepStrictEqual(normalizeConfigData.args[0], [
-                { settings: { name: filePath } },
-                createContext({ cwd: tempDir }, void 0, name, filePath, basePath)
-            ]);
-        });
-
-        it("should return a config array that contains the yielded elements from '_normalizeConfigData(configData, ctx)'.", () => {
-            const elements = [{}, {}];
-
-            factory._normalizeConfigData = () => elements; // eslint-disable-line no-underscore-dangle
-
-            const configArray = factory.loadFile("js/.eslintrc.js");
-
-            assert.strictEqual(configArray.length, 2);
-            assert.strictEqual(configArray[0], elements[0]);
-            assert.strictEqual(configArray[1], elements[1]);
-        });
-    });
-
-    describe("'loadInDirectory(directoryPath, options)' method should load the config file of a directory.", () => {
-        const basicFiles = {
-            "js/.eslintrc.js": "exports.settings = { name: 'js/.eslintrc.js' }",
-            "cjs/.eslintrc.cjs": "exports.settings = { name: 'cjs/.eslintrc.cjs' }",
-            "json/.eslintrc.json": "{ \"settings\": { \"name\": \"json/.eslintrc.json\" } }",
-            "legacy-json/.eslintrc": "{ \"settings\": { \"name\": \"legacy-json/.eslintrc\" } }",
-            "legacy-yml/.eslintrc": "settings:\n  name: legacy-yml/.eslintrc",
-            "package-json/package.json": "{ \"eslintConfig\": { \"settings\": { \"name\": \"package-json/package.json\" } } }",
-            "yml/.eslintrc.yml": "settings:\n  name: yml/.eslintrc.yml",
-            "yaml/.eslintrc.yaml": "settings:\n  name: yaml/.eslintrc.yaml"
-        };
-        const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-            cwd: () => tempDir,
-            files: {
-                ...basicFiles,
-                "invalid-property/.eslintrc.json": "{ \"files\": \"*.js\" }",
-                "package-json-no-config/package.json": "{ \"name\": \"foo\" }"
-            }
-        });
-
-        /** @type {ConfigArrayFactory} */
-        let factory;
-
-        beforeEach(() => {
-            factory = new ConfigArrayFactory();
-        });
-
-        it("should throw an error if 'directoryPath' is null.", () => {
-            assert.throws(() => factory.loadInDirectory(null));
-        });
-
-        it("should return an empty config array if the config file of 'directoryPath' doesn't exist.", () => {
-            assert.strictEqual(factory.loadInDirectory("non-exist").length, 0);
-        });
-
-        it("should return an empty config array if the config file of 'directoryPath' was package.json and it didn't have 'eslintConfig' field.", () => {
-            assert.strictEqual(factory.loadInDirectory("package-json-no-config").length, 0);
-        });
-
-        it("should throw an error if the config data had invalid properties,", () => {
-            assert.throws(() => {
-                factory.loadInDirectory("invalid-property");
-            }, /Unexpected top-level property "files"/u);
-        });
-
-        for (const filePath of Object.keys(basicFiles)) {
-            const directoryPath = filePath.split("/")[0];
-
-            it(`should load '${directoryPath}' then return a config array what contains the config file of that directory.`, () => { // eslint-disable-line no-loop-func
-                const configArray = factory.loadInDirectory(directoryPath);
-
-                assert.strictEqual(configArray.length, 1);
-                assertConfigArrayElement(configArray[0], {
-                    filePath: path.resolve(tempDir, filePath),
-                    name: path.relative(tempDir, path.resolve(tempDir, filePath)),
-                    settings: { name: filePath }
-                });
-            });
-        }
-
-        it("should call '_normalizeConfigData(configData, ctx)' with the loaded config data and given options.", () => {
-            const basePath = tempDir;
-            const directoryPath = "js";
-            const name = "example";
-            const normalizeConfigData = spy(factory, "_normalizeConfigData");
-
-            factory.loadInDirectory(directoryPath, { basePath, name });
-
-            assert.strictEqual(normalizeConfigData.callCount, 1);
-            assert.deepStrictEqual(normalizeConfigData.args[0], [
-                { settings: { name: `${directoryPath}/.eslintrc.js` } },
-                createContext({ cwd: tempDir }, void 0, name, path.join(directoryPath, ".eslintrc.js"), basePath)
-            ]);
-        });
-
-        it("should return a config array that contains the yielded elements from '_normalizeConfigData(configData, ctx)'.", () => {
-            const elements = [{}, {}];
-
-            factory._normalizeConfigData = () => elements; // eslint-disable-line no-underscore-dangle
-
-            const configArray = factory.loadInDirectory("js");
-
-            assert.strictEqual(configArray.length, 2);
-            assert.strictEqual(configArray[0], elements[0]);
-            assert.strictEqual(configArray[1], elements[1]);
-        });
-    });
-
-    /*
-     * All of `create`, `loadFile`, and `loadInDirectory` call this method.
-     * So this section tests the common part of the three.
-     */
-    describe("'_normalizeConfigData(configData, ctx)' method should normalize the config data.", () => {
-
-        /** @type {ConfigArrayFactory} */
-        let factory = null;
-
-        /**
-         * Call `_normalizeConfigData` method with given arguments.
-         * @param {ConfigData} configData The config data to normalize.
-         * @param {Object} [options] The options.
-         * @param {string} [options.filePath] The path to the config file of the config data.
-         * @param {string} [options.name] The name of the config file of the config data.
-         * @returns {ConfigArray} The created config array.
-         */
-        function create(configData, { filePath, name } = {}) {
-            const ctx = createContext({ cwd: tempDir }, void 0, name, filePath, void 0);
-
-            return new ConfigArray(...factory._normalizeConfigData(configData, ctx)); // eslint-disable-line no-underscore-dangle
-        }
-
-        describe("misc", () => {
-            before(() => {
-                const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                    cwd: () => tempDir
-                });
-
-                factory = new ConfigArrayFactory();
-            });
-
-            describe("if the config data was empty, the returned value", () => {
-                let configArray;
-
-                beforeEach(() => {
-                    configArray = create({});
-                });
-
-                it("should have an element.", () => {
-                    assert.strictEqual(configArray.length, 1);
-                });
-
-                it("should have the default values in the element.", () => {
-                    assertConfigArrayElement(configArray[0], {});
-                });
-            });
-
-            describe("if the config data had 'env' property, the returned value", () => {
-                const env = { node: true };
-                let configArray;
-
-                beforeEach(() => {
-                    configArray = create({ env });
-                });
-
-                it("should have an element.", () => {
-                    assert.strictEqual(configArray.length, 1);
-                });
-
-                it("should have the 'env' value in the element.", () => {
-                    assertConfigArrayElement(configArray[0], { env });
-                });
-            });
-
-            describe("if the config data had 'globals' property, the returned value", () => {
-                const globals = { window: "readonly" };
-                let configArray;
-
-                beforeEach(() => {
-                    configArray = create({ globals });
-                });
-
-                it("should have an element.", () => {
-                    assert.strictEqual(configArray.length, 1);
-                });
-
-                it("should have the 'globals' value in the element.", () => {
-                    assertConfigArrayElement(configArray[0], { globals });
-                });
-            });
-
-            describe("if the config data had 'parser' property, the returned value", () => {
-                const parser = "espree";
-                let configArray;
-
-                beforeEach(() => {
-                    configArray = create({ parser });
-                });
-
-                it("should have an element.", () => {
-                    assert.strictEqual(configArray.length, 1);
-                });
-
-                it("should have the 'parser' value in the element.", () => {
-                    assert.strictEqual(configArray[0].parser.id, parser);
-                });
-            });
-
-            describe("if the config data had 'parserOptions' property, the returned value", () => {
-                const parserOptions = { ecmaVersion: 2015 };
-                let configArray;
-
-                beforeEach(() => {
-                    configArray = create({ parserOptions });
-                });
-
-                it("should have an element.", () => {
-                    assert.strictEqual(configArray.length, 1);
-                });
-
-                it("should have the 'parserOptions' value in the element.", () => {
-                    assertConfigArrayElement(configArray[0], { parserOptions });
-                });
-            });
-
-            describe("if the config data had 'plugins' property, the returned value", () => {
-                const plugins = [];
-                let configArray;
-
-                beforeEach(() => {
-                    configArray = create({ plugins });
-                });
-
-                it("should have an element.", () => {
-                    assert.strictEqual(configArray.length, 1);
-                });
-
-                it("should have the 'plugins' value in the element.", () => {
-                    assertConfigArrayElement(configArray[0], { plugins: {} });
-                });
-            });
-
-            describe("if the config data had 'root' property, the returned value", () => {
-                const root = true;
-                let configArray;
-
-                beforeEach(() => {
-                    configArray = create({ root });
-                });
-
-                it("should have an element.", () => {
-                    assert.strictEqual(configArray.length, 1);
-                });
-
-                it("should have the 'root' value in the element.", () => {
-                    assertConfigArrayElement(configArray[0], { root });
-                });
-            });
-
-            describe("if the config data had 'rules' property, the returned value", () => {
-                const rules = { eqeqeq: "error" };
-                let configArray;
-
-                beforeEach(() => {
-                    configArray = create({ rules });
-                });
-
-                it("should have an element.", () => {
-                    assert.strictEqual(configArray.length, 1);
-                });
-
-                it("should have the 'rules' value in the element.", () => {
-                    assertConfigArrayElement(configArray[0], { rules });
-                });
-            });
-
-            describe("if the config data had 'settings' property, the returned value", () => {
-                const settings = { foo: 777 };
-                let configArray;
-
-                beforeEach(() => {
-                    configArray = create({ settings });
-                });
-
-                it("should have an element.", () => {
-                    assert.strictEqual(configArray.length, 1);
-                });
-
-                it("should have the 'settings' value in the element.", () => {
-                    assertConfigArrayElement(configArray[0], { settings });
-                });
-            });
-        });
-
-        describe("'parser' details", () => {
-            before(() => {
-                const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                    cwd: () => tempDir,
-                    files: {
-                        "node_modules/xxx-parser/index.js": "exports.name = 'xxx-parser';",
-                        "subdir/node_modules/xxx-parser/index.js": "exports.name = 'subdir/xxx-parser';",
-                        "parser.js": "exports.name = './parser.js';"
-                    }
-                });
-
-                factory = new ConfigArrayFactory();
-            });
-
-            describe("if the 'parser' property was a valid package, the first config array element", () => {
-                let element;
-
-                beforeEach(() => {
-                    element = create({ parser: "xxx-parser" })[0];
-                });
-
-                it("should have the package ID at 'parser.id' property.", () => {
-                    assert.strictEqual(element.parser.id, "xxx-parser");
-                });
-
-                it("should have the package object at 'parser.definition' property.", () => {
-                    assert.deepStrictEqual(element.parser.definition, { name: "xxx-parser" });
-                });
-
-                it("should have the path to the package at 'parser.filePath' property.", () => {
-                    assert.strictEqual(element.parser.filePath, path.join(tempDir, "node_modules/xxx-parser/index.js"));
-                });
-            });
-
-            describe("if the 'parser' property was an invalid package, the first config array element", () => {
-                let element;
-
-                beforeEach(() => {
-                    element = create({ parser: "invalid-parser" })[0];
-                });
-
-                it("should have the package ID at 'parser.id' property.", () => {
-                    assert.strictEqual(element.parser.id, "invalid-parser");
-                });
-
-                it("should have the loading error at 'parser.error' property.", () => {
-                    assert.match(element.parser.error.message, /Cannot find module 'invalid-parser'/u);
-                });
-            });
-
-            describe("if the 'parser' property was a valid relative path, the first config array element", () => {
-                let element;
-
-                beforeEach(() => {
-                    element = create({ parser: "./parser" })[0];
-                });
-
-                it("should have the given path at 'parser.id' property.", () => {
-                    assert.strictEqual(element.parser.id, "./parser");
-                });
-
-                it("should have the file's object at 'parser.definition' property.", () => {
-                    assert.deepStrictEqual(element.parser.definition, { name: "./parser.js" });
-                });
-
-                it("should have the absolute path to the file at 'parser.filePath' property.", () => {
-                    assert.strictEqual(element.parser.filePath, path.join(tempDir, "./parser.js"));
-                });
-            });
-
-            describe("if the 'parser' property was an invalid relative path, the first config array element", () => {
-                let element;
-
-                beforeEach(() => {
-                    element = create({ parser: "./invalid-parser" })[0];
-                });
-
-                it("should have the given path at 'parser.id' property.", () => {
-                    assert.strictEqual(element.parser.id, "./invalid-parser");
-                });
-
-                it("should have the loading error at 'parser.error' property.", () => {
-                    assert.match(element.parser.error.message, /Cannot find module '.\/invalid-parser'/u);
-                });
-            });
-
-            describe("if 'parser' property was given and 'filePath' option was given, the parser", () => {
-                let element;
-
-                beforeEach(() => {
-                    element = create(
-                        { parser: "xxx-parser" },
-                        { filePath: path.join(tempDir, "subdir/.eslintrc") }
-                    )[0];
-                });
-
-                it("should be resolved relative to the 'filePath' option.", () => {
-                    assert.strictEqual(
-                        element.parser.filePath,
-
-                        // rather than "xxx-parser" at the project root.
-                        path.join(tempDir, "subdir/node_modules/xxx-parser/index.js")
-                    );
-                });
-            });
-        });
-
-        describe("'plugins' details", () => {
-            before(() => {
-                const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                    cwd: () => tempDir,
-                    files: {
-                        "node_modules/eslint-plugin-ext/index.js": "exports.processors = { '.abc': {}, '.xyz': {}, other: {} };",
-                        "node_modules/eslint-plugin-subdir/index.js": "",
-                        "node_modules/eslint-plugin-xxx/index.js": "exports.configs = { name: 'eslint-plugin-xxx' };",
-                        "subdir/node_modules/eslint-plugin-subdir/index.js": "",
-                        "parser.js": ""
-                    }
-                });
-
-                factory = new ConfigArrayFactory();
-            });
-
-            it("should throw an error if a 'plugins' value is a file path.", () => {
-                assert.throws(() => {
-                    create({ plugins: ["./path/to/plugin"] });
-                }, /Plugins array cannot includes file paths/u);
-            });
-
-            describe("if the 'plugins' property was a valid package, the first config array element", () => {
-                let element;
-
-                beforeEach(() => {
-                    element = create({ plugins: ["xxx"] })[0];
-                });
-
-                it("should have 'plugins[id]' property.", () => {
-                    assert.notStrictEqual(element.plugins.xxx, void 0);
-                });
-
-                it("should have the package ID at 'plugins[id].id' property.", () => {
-                    assert.strictEqual(element.plugins.xxx.id, "xxx");
-                });
-
-                it("should have the package object at 'plugins[id].definition' property.", () => {
-                    assertPluginDefinition(
-                        element.plugins.xxx.definition,
-                        { configs: { name: "eslint-plugin-xxx" } }
-                    );
-                });
-
-                it("should have the path to the package at 'plugins[id].filePath' property.", () => {
-                    assert.strictEqual(element.plugins.xxx.filePath, path.join(tempDir, "node_modules/eslint-plugin-xxx/index.js"));
-                });
-            });
-
-            describe("if the 'plugins' property was an invalid package, the first config array element", () => {
-                let element;
-
-                beforeEach(() => {
-                    element = create({ plugins: ["invalid"] })[0];
-                });
-
-                it("should have 'plugins[id]' property.", () => {
-                    assert.notStrictEqual(element.plugins.invalid, void 0);
-                });
-
-                it("should have the package ID at 'plugins[id].id' property.", () => {
-                    assert.strictEqual(element.plugins.invalid.id, "invalid");
-                });
-
-                it("should have the loading error at 'plugins[id].error' property.", () => {
-                    assert.match(element.plugins.invalid.error.message, /Cannot find module 'eslint-plugin-invalid'/u);
-                });
-            });
-
-            describe("even if 'plugins' property was given and 'filePath' option was given,", () => {
-                it("should load the plugin from 'subdir'.", () => {
-                    const configArray = create(
-                        { plugins: ["subdir"] },
-                        { filePath: path.resolve(tempDir, "subdir/a.js") }
-                    );
-
-                    assert.strictEqual(
-                        configArray[0].plugins.subdir.filePath,
-                        path.resolve(tempDir, "subdir/node_modules/eslint-plugin-subdir/index.js")
-                    );
-                });
-            });
-
-            describe("if 'plugins' property was given and the plugin has two file extension processors, the returned value", () => {
-                let configArray;
-
-                beforeEach(() => {
-                    configArray = create({ plugins: ["ext"] });
-                });
-
-                it("should have three elements.", () => {
-                    assert.strictEqual(configArray.length, 3);
-                });
-
-                describe("the first element", () => {
-                    let element;
-
-                    beforeEach(() => {
-                        element = configArray[0];
-                    });
-
-                    it("should be named '#processors[\"ext/.abc\"]'.", () => {
-                        assert.strictEqual(element.name, "#processors[\"ext/.abc\"]");
-                    });
-
-                    it("should not have 'plugins' property.", () => {
-                        assert.strictEqual(element.plugins, void 0);
-                    });
-
-                    it("should have 'processor' property.", () => {
-                        assert.strictEqual(element.processor, "ext/.abc");
-                    });
-
-                    it("should have 'criteria' property which matches '.abc'.", () => {
-                        assert.strictEqual(element.criteria.test(path.join(tempDir, "1234.abc")), true);
-                        assert.strictEqual(element.criteria.test(path.join(tempDir, "1234.xyz")), false);
-                    });
-                });
-
-                describe("the second element", () => {
-                    let element;
-
-                    beforeEach(() => {
-                        element = configArray[1];
-                    });
-
-                    it("should be named '#processors[\"ext/.xyz\"]'.", () => {
-                        assert.strictEqual(element.name, "#processors[\"ext/.xyz\"]");
-                    });
-
-                    it("should not have 'plugins' property.", () => {
-                        assert.strictEqual(element.plugins, void 0);
-                    });
-
-                    it("should have 'processor' property.", () => {
-                        assert.strictEqual(element.processor, "ext/.xyz");
-                    });
-
-                    it("should have 'criteria' property which matches '.xyz'.", () => {
-                        assert.strictEqual(element.criteria.test(path.join(tempDir, "1234.abc")), false);
-                        assert.strictEqual(element.criteria.test(path.join(tempDir, "1234.xyz")), true);
-                    });
-                });
-
-                describe("the third element", () => {
-                    let element;
-
-                    beforeEach(() => {
-                        element = configArray[2];
-                    });
-
-                    it("should have 'plugins' property.", () => {
-                        assert.strictEqual(element.plugins.ext.id, "ext");
-                    });
-
-                    it("should not have 'processor' property.", () => {
-                        assert.strictEqual(element.processor, void 0);
-                    });
-                });
-            });
-        });
-
-        describe("'extends' details", () => {
-            before(() => {
-                const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                    cwd: () => tempDir,
-                    files: {
-                        "node_modules/eslint-config-foo/index.js": "exports.env = { browser: true }",
-                        "node_modules/eslint-config-one/index.js": "module.exports = { extends: 'two', env: { browser: true } }",
-                        "node_modules/eslint-config-two/index.js": "module.exports = { env: { node: true } }",
-                        "node_modules/eslint-config-override/index.js": `
-                            module.exports = {
-                                rules: { regular: 1 },
-                                overrides: [
-                                    { files: '*.xxx', rules: { override: 1 } },
-                                    { files: '*.yyy', rules: { override: 2 } }
-                                ]
-                            }
-                        `,
-                        "node_modules/eslint-plugin-foo/index.js": "exports.configs = { bar: { env: { es6: true } } }",
-                        "node_modules/eslint-plugin-invalid-config/index.js": "exports.configs = { foo: {} }",
-                        "node_modules/eslint-plugin-error/index.js": "throw new Error('xxx error')",
-                        "base.js": "module.exports = { rules: { semi: [2, 'always'] } };"
-                    }
-                });
-
-                factory = new ConfigArrayFactory();
-            });
-
-            it("should throw an error when extends config module is not found", () => {
-                assert.throws(() => {
-                    create({
-                        extends: "not-exist",
-                        rules: { eqeqeq: 2 }
-                    });
-                }, /Failed to load config "not-exist" to extend from./u);
-            });
-
-            it("should throw an error when an eslint config is not found", () => {
-                assert.throws(() => {
-                    create({
-                        extends: "eslint:foo",
-                        rules: { eqeqeq: 2 }
-                    });
-                }, /Failed to load config "eslint:foo" to extend from./u);
-            });
-
-            it("should throw an error when a plugin threw while loading.", () => {
-                assert.throws(() => {
-                    create({
-                        extends: "plugin:error/foo",
-                        rules: { eqeqeq: 2 }
-                    });
-                }, /xxx error/u);
-            });
-
-            it("should throw an error when a plugin extend is a file path.", () => {
-                assert.throws(() => {
-                    create({
-                        extends: "plugin:./path/to/foo",
-                        rules: { eqeqeq: 2 }
-                    });
-                }, /'extends' cannot use a file path for plugins/u);
-            });
-
-            it("should throw an error when an eslint config is not found", () => {
-                assert.throws(() => {
-                    create({
-                        extends: "eslint:foo",
-                        rules: { eqeqeq: 2 }
-                    });
-                }, /Failed to load config "eslint:foo" to extend from./u);
-            });
-
-            describe("if 'extends' property was 'eslint:all', the returned value", () => {
-                let configArray;
-
-                beforeEach(() => {
-                    configArray = create(
-                        { extends: "eslint:all", rules: { eqeqeq: 1 } },
-                        { name: ".eslintrc" }
-                    );
-                });
-
-                it("should have two elements.", () => {
-                    assert.strictEqual(configArray.length, 2);
-                });
-
-                it("should have the config data of 'eslint:all' at the first element.", () => {
-                    assertConfigArrayElement(configArray[0], {
-                        name: ".eslintrc » eslint:all",
-                        filePath: require.resolve("../../../conf/eslint-all.js"),
-                        ...require("../../../conf/eslint-all.js")
-                    });
-                });
-
-                it("should have the given config data at the second element.", () => {
-                    assertConfigArrayElement(configArray[1], {
-                        name: ".eslintrc",
-                        rules: { eqeqeq: 1 }
-                    });
-                });
-            });
-
-            describe("if 'extends' property was 'eslint:recommended', the returned value", () => {
-                let configArray;
-
-                beforeEach(() => {
-                    configArray = create(
-                        { extends: "eslint:recommended", rules: { eqeqeq: 1 } },
-                        { name: ".eslintrc" }
-                    );
-                });
-
-                it("should have two elements.", () => {
-                    assert.strictEqual(configArray.length, 2);
-                });
-
-                it("should have the config data of 'eslint:recommended' at the first element.", () => {
-                    assertConfigArrayElement(configArray[0], {
-                        name: ".eslintrc » eslint:recommended",
-                        filePath: require.resolve("../../../conf/eslint-recommended.js"),
-                        ...require("../../../conf/eslint-recommended.js")
-                    });
-                });
-
-                it("should have the given config data at the second element.", () => {
-                    assertConfigArrayElement(configArray[1], {
-                        name: ".eslintrc",
-                        rules: { eqeqeq: 1 }
-                    });
-                });
-            });
-
-            describe("if 'extends' property was 'foo', the returned value", () => {
-                let configArray;
-
-                beforeEach(() => {
-                    configArray = create(
-                        { extends: "foo", rules: { eqeqeq: 1 } },
-                        { name: ".eslintrc" }
-                    );
-                });
-
-                it("should have two elements.", () => {
-                    assert.strictEqual(configArray.length, 2);
-                });
-
-                it("should have the config data of 'eslint-config-foo' at the first element.", () => {
-                    assertConfigArrayElement(configArray[0], {
-                        name: ".eslintrc » eslint-config-foo",
-                        filePath: path.join(tempDir, "node_modules/eslint-config-foo/index.js"),
-                        env: { browser: true }
-                    });
-                });
-
-                it("should have the given config data at the second element.", () => {
-                    assertConfigArrayElement(configArray[1], {
-                        name: ".eslintrc",
-                        rules: { eqeqeq: 1 }
-                    });
-                });
-            });
-
-            describe("if 'extends' property was 'plugin:foo/bar', the returned value", () => {
-                let configArray;
-
-                beforeEach(() => {
-                    configArray = create(
-                        { extends: "plugin:foo/bar", rules: { eqeqeq: 1 } },
-                        { name: ".eslintrc" }
-                    );
-                });
-
-                it("should have two elements.", () => {
-                    assert.strictEqual(configArray.length, 2);
-                });
-
-                it("should have the config data of 'plugin:foo/bar' at the first element.", () => {
-                    assertConfigArrayElement(configArray[0], {
-                        name: ".eslintrc » plugin:foo/bar",
-                        filePath: path.join(tempDir, "node_modules/eslint-plugin-foo/index.js"),
-                        env: { es6: true }
-                    });
-                });
-
-                it("should have the given config data at the second element.", () => {
-                    assertConfigArrayElement(configArray[1], {
-                        name: ".eslintrc",
-                        rules: { eqeqeq: 1 }
-                    });
-                });
-            });
-
-            describe("if 'extends' property was './base', the returned value", () => {
-                let configArray;
-
-                beforeEach(() => {
-                    configArray = create(
-                        { extends: "./base", rules: { eqeqeq: 1 } },
-                        { name: ".eslintrc" }
-                    );
-                });
-
-                it("should have two elements.", () => {
-                    assert.strictEqual(configArray.length, 2);
-                });
-
-                it("should have the config data of './base' at the first element.", () => {
-                    assertConfigArrayElement(configArray[0], {
-                        name: ".eslintrc » ./base",
-                        filePath: path.join(tempDir, "base.js"),
-                        rules: { semi: [2, "always"] }
-                    });
-                });
-
-                it("should have the given config data at the second element.", () => {
-                    assertConfigArrayElement(configArray[1], {
-                        name: ".eslintrc",
-                        rules: { eqeqeq: 1 }
-                    });
-                });
-            });
-
-            describe("if 'extends' property was 'one' and the 'one' extends 'two', the returned value", () => {
-                let configArray;
-
-                beforeEach(() => {
-                    configArray = create(
-                        { extends: "one", rules: { eqeqeq: 1 } },
-                        { name: ".eslintrc" }
-                    );
-                });
-
-                it("should have three elements.", () => {
-                    assert.strictEqual(configArray.length, 3);
-                });
-
-                it("should have the config data of 'eslint-config-two' at the first element.", () => {
-                    assertConfigArrayElement(configArray[0], {
-                        name: ".eslintrc » eslint-config-one » eslint-config-two",
-                        filePath: path.join(tempDir, "node_modules/eslint-config-two/index.js"),
-                        env: { node: true }
-                    });
-                });
-
-                it("should have the config data of 'eslint-config-one' at the second element.", () => {
-                    assertConfigArrayElement(configArray[1], {
-                        name: ".eslintrc » eslint-config-one",
-                        filePath: path.join(tempDir, "node_modules/eslint-config-one/index.js"),
-                        env: { browser: true }
-                    });
-                });
-
-                it("should have the given config data at the third element.", () => {
-                    assertConfigArrayElement(configArray[2], {
-                        name: ".eslintrc",
-                        rules: { eqeqeq: 1 }
-                    });
-                });
-            });
-
-            describe("if 'extends' property was 'override' and the 'override' has 'overrides' property, the returned value", () => {
-                let configArray;
-
-                beforeEach(() => {
-                    configArray = create(
-                        { extends: "override", rules: { eqeqeq: 1 } },
-                        { name: ".eslintrc" }
-                    );
-                });
-
-                it("should have four elements.", () => {
-                    assert.strictEqual(configArray.length, 4);
-                });
-
-                it("should have the config data of 'eslint-config-override' at the first element.", () => {
-                    assertConfigArrayElement(configArray[0], {
-                        name: ".eslintrc » eslint-config-override",
-                        filePath: path.join(tempDir, "node_modules/eslint-config-override/index.js"),
-                        rules: { regular: 1 }
-                    });
-                });
-
-                it("should have the 'overrides[0]' config data of 'eslint-config-override' at the second element.", () => {
-                    assertConfigArrayElement(configArray[1], {
-                        name: ".eslintrc » eslint-config-override#overrides[0]",
-                        filePath: path.join(tempDir, "node_modules/eslint-config-override/index.js"),
-                        criteria: OverrideTester.create(["*.xxx"], [], tempDir),
-                        rules: { override: 1 }
-                    });
-                });
-
-                it("should have the 'overrides[1]' config data of 'eslint-config-override' at the third element.", () => {
-                    assertConfigArrayElement(configArray[2], {
-                        name: ".eslintrc » eslint-config-override#overrides[1]",
-                        filePath: path.join(tempDir, "node_modules/eslint-config-override/index.js"),
-                        criteria: OverrideTester.create(["*.yyy"], [], tempDir),
-                        rules: { override: 2 }
-                    });
-                });
-
-                it("should have the given config data at the fourth element.", () => {
-                    assertConfigArrayElement(configArray[3], {
-                        name: ".eslintrc",
-                        rules: { eqeqeq: 1 }
-                    });
-                });
-            });
-        });
-
-        describe("'overrides' details", () => {
-            before(() => {
-                const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                    cwd: () => tempDir,
-                    files: {
-                        "node_modules/eslint-config-foo/index.js": `
-                            module.exports = {
-                                rules: { eqeqeq: "error" }
-                            }
-                        `,
-                        "node_modules/eslint-config-has-overrides/index.js": `
-                            module.exports = {
-                                rules: { eqeqeq: "error" },
-                                overrides: [
-                                    {
-                                        files: ["**/foo/**/*.js"],
-                                        rules: { eqeqeq: "off" }
-                                    }
-                                ]
-                            }
-                        `,
-                        "node_modules/eslint-config-root/index.js": `
-                            module.exports = {
-                                root: true,
-                                rules: { eqeqeq: "error" }
-                            }
-                        `
-                    }
-                });
-
-                factory = new ConfigArrayFactory();
-            });
-
-            describe("if 'overrides' property was given, the returned value", () => {
-                let configArray;
-
-                beforeEach(() => {
-                    configArray = create({
-                        rules: { regular: 1 },
-                        overrides: [
-                            { files: "*.xxx", rules: { override: 1 } },
-                            { files: "*.yyy", rules: { override: 2 } }
-                        ]
-                    });
-                });
-
-                it("should have three elements.", () => {
-                    assert.strictEqual(configArray.length, 3);
-                });
-
-                it("should have the given config data at the first element.", () => {
-                    assertConfigArrayElement(configArray[0], {
-                        rules: { regular: 1 }
-                    });
-                });
-
-                it("should have the config data of 'overrides[0]' at the second element.", () => {
-                    assertConfigArrayElement(configArray[1], {
-                        name: "#overrides[0]",
-                        criteria: OverrideTester.create(["*.xxx"], [], tempDir),
-                        rules: { override: 1 }
-                    });
-                });
-
-                it("should have the config data of 'overrides[1]' at the third element.", () => {
-                    assertConfigArrayElement(configArray[2], {
-                        name: "#overrides[1]",
-                        criteria: OverrideTester.create(["*.yyy"], [], tempDir),
-                        rules: { override: 2 }
-                    });
-                });
-            });
-
-            describe("if a config in 'overrides' property had 'extends' property, the returned value", () => {
-                let configArray;
-
-                beforeEach(() => {
-                    configArray = create({
-                        rules: { regular: 1 },
-                        overrides: [
-                            {
-                                files: "*.xxx",
-                                extends: "foo",
-                                rules: { override: 1 }
-                            }
-                        ]
-                    });
-                });
-
-                it("should have three elements.", () => {
-                    assert.strictEqual(configArray.length, 3);
-                });
-
-                it("should have the given config data at the first element.", () => {
-                    assertConfigArrayElement(configArray[0], {
-                        rules: { regular: 1 }
-                    });
-                });
-
-                it("should have the config data of 'overrides[0] » eslint-config-foo' at the second element.", () => {
-                    assertConfigArrayElement(configArray[1], {
-                        name: "#overrides[0] » eslint-config-foo",
-                        filePath: path.join(tempDir, "node_modules/eslint-config-foo/index.js"),
-                        criteria: OverrideTester.create(["*.xxx"], [], tempDir),
-                        rules: { eqeqeq: "error" }
-                    });
-                });
-
-                it("should have the config data of 'overrides[0]' at the third element.", () => {
-                    assertConfigArrayElement(configArray[2], {
-                        name: "#overrides[0]",
-                        criteria: OverrideTester.create(["*.xxx"], [], tempDir),
-                        rules: { override: 1 }
-                    });
-                });
-            });
-
-            describe("if a config in 'overrides' property had 'extends' property and the shareable config has 'overrides' property, the returned value", () => {
-                let configArray;
-
-                beforeEach(() => {
-                    configArray = create({
-                        rules: { regular: 1 },
-                        overrides: [
-                            {
-                                files: "*.xxx",
-                                extends: "has-overrides",
-                                rules: { override: 1 }
-                            }
-                        ]
-                    });
-                });
-
-                it("should have four elements.", () => {
-                    assert.strictEqual(configArray.length, 4);
-                });
-
-                it("should have the given config data at the first element.", () => {
-                    assertConfigArrayElement(configArray[0], {
-                        rules: { regular: 1 }
-                    });
-                });
-
-                it("should have the config data of 'overrides[0] » eslint-config-has-overrides' at the second element.", () => {
-                    assertConfigArrayElement(configArray[1], {
-                        name: "#overrides[0] » eslint-config-has-overrides",
-                        filePath: path.join(tempDir, "node_modules/eslint-config-has-overrides/index.js"),
-                        criteria: OverrideTester.create(["*.xxx"], [], tempDir),
-                        rules: { eqeqeq: "error" }
-                    });
-                });
-
-                it("should have the config data of 'overrides[0] » eslint-config-has-overrides#overrides[0]' at the third element.", () => {
-                    assertConfigArrayElement(configArray[2], {
-                        name: "#overrides[0] » eslint-config-has-overrides#overrides[0]",
-                        filePath: path.join(tempDir, "node_modules/eslint-config-has-overrides/index.js"),
-                        criteria: OverrideTester.and(
-                            OverrideTester.create(["*.xxx"], [], tempDir),
-                            OverrideTester.create(["**/foo/**/*.js"], [], tempDir)
-                        ),
-                        rules: { eqeqeq: "off" }
-                    });
-                });
-
-                it("should have the config data of 'overrides[0]' at the fourth element.", () => {
-                    assertConfigArrayElement(configArray[3], {
-                        name: "#overrides[0]",
-                        criteria: OverrideTester.create(["*.xxx"], [], tempDir),
-                        rules: { override: 1 }
-                    });
-                });
-            });
-
-            describe("if a config in 'overrides' property had 'overrides' property, the returned value", () => {
-                let configArray;
-
-                beforeEach(() => {
-                    configArray = create({
-                        rules: { regular: 1 },
-                        overrides: [
-                            {
-                                files: "*.xxx",
-                                rules: { override: 1 },
-                                overrides: [
-                                    {
-                                        files: "*.yyy",
-                                        rules: { override: 2 }
-                                    }
-                                ]
-                            }
-                        ]
-                    });
-                });
-
-                it("should have three elements.", () => {
-                    assert.strictEqual(configArray.length, 3);
-                });
-
-                it("should have the given config data at the first element.", () => {
-                    assertConfigArrayElement(configArray[0], {
-                        rules: { regular: 1 }
-                    });
-                });
-
-                it("should have the config data of 'overrides[0]' at the second element.", () => {
-                    assertConfigArrayElement(configArray[1], {
-                        name: "#overrides[0]",
-                        criteria: OverrideTester.create(["*.xxx"], [], tempDir),
-                        rules: { override: 1 }
-                    });
-                });
-
-                it("should have the config data of 'overrides[0].overrides[0]' at the third element.", () => {
-                    assertConfigArrayElement(configArray[2], {
-                        name: "#overrides[0]#overrides[0]",
-                        criteria: OverrideTester.and(
-                            OverrideTester.create(["*.xxx"], [], tempDir),
-                            OverrideTester.create(["*.yyy"], [], tempDir)
-                        ),
-                        rules: { override: 2 }
-                    });
-                });
-            });
-
-            describe("if a config in 'overrides' property had 'root' property, the returned value", () => {
-                let configArray;
-
-                beforeEach(() => {
-                    configArray = create({
-                        rules: { regular: 1 },
-                        overrides: [
-                            {
-                                files: "*.xxx",
-                                extends: "root",
-                                rules: { override: 1 }
-                            }
-                        ]
-                    });
-                });
-
-                it("should have three elements.", () => {
-                    assert.strictEqual(configArray.length, 3);
-                });
-
-                it("should have the given config data at the first element.", () => {
-                    assertConfigArrayElement(configArray[0], {
-                        rules: { regular: 1 }
-                    });
-                });
-
-                it("should have the config data of 'overrides[0] » eslint-config-root' at the second element; it doesn't have 'root' property.", () => {
-                    assertConfigArrayElement(configArray[1], {
-                        name: "#overrides[0] » eslint-config-root",
-                        filePath: path.join(tempDir, "node_modules/eslint-config-root/index.js"),
-                        criteria: OverrideTester.create(["*.xxx"], [], tempDir),
-                        rules: { eqeqeq: "error" }
-                    });
-                });
-
-                it("should have the config data of 'overrides[0]' at the third element.", () => {
-                    assertConfigArrayElement(configArray[2], {
-                        name: "#overrides[0]",
-                        criteria: OverrideTester.create(["*.xxx"], [], tempDir),
-                        rules: { override: 1 }
-                    });
-                });
-            });
-        });
-
-        describe("additional plugin pool", () => {
-            beforeEach(() => {
-                const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                    cwd: () => tempDir
-                });
-
-                factory = new ConfigArrayFactory({
-                    additionalPluginPool: new Map([
-                        ["abc", { configs: { name: "abc" } }],
-                        ["eslint-plugin-def", { configs: { name: "def" } }]
-                    ])
-                });
-            });
-
-            it("should use the matched plugin in the additional plugin pool; short to short", () => {
-                const configArray = create({ plugins: ["abc"] });
-
-                assert.strictEqual(configArray[0].plugins.abc.id, "abc");
-                assertPluginDefinition(
-                    configArray[0].plugins.abc.definition,
-                    { configs: { name: "abc" } }
-                );
-            });
-
-            it("should use the matched plugin in the additional plugin pool; long to short", () => {
-                const configArray = create({ plugins: ["eslint-plugin-abc"] });
-
-                assert.strictEqual(configArray[0].plugins.abc.id, "abc");
-                assertPluginDefinition(
-                    configArray[0].plugins.abc.definition,
-                    { configs: { name: "abc" } }
-                );
-            });
-
-            it("should use the matched plugin in the additional plugin pool; short to long", () => {
-                const configArray = create({ plugins: ["def"] });
-
-                assert.strictEqual(configArray[0].plugins.def.id, "def");
-                assertPluginDefinition(
-                    configArray[0].plugins.def.definition,
-                    { configs: { name: "def" } }
-                );
-            });
-
-            it("should use the matched plugin in the additional plugin pool; long to long", () => {
-                const configArray = create({ plugins: ["eslint-plugin-def"] });
-
-                assert.strictEqual(configArray[0].plugins.def.id, "def");
-                assertPluginDefinition(
-                    configArray[0].plugins.def.definition,
-                    { configs: { name: "def" } }
-                );
-            });
-        });
-    });
-
-    // This group moved from 'tests/lib/config/config-file.js' when refactoring to keep the cumulated test cases.
-    describe("'extends' property should handle the content of extended configs properly.", () => {
-        const files = {
-            "node_modules/eslint-config-foo/index.js": "exports.env = { browser: true }",
-            "node_modules/eslint-config-one/index.js": "module.exports = { extends: 'two', env: { browser: true } }",
-            "node_modules/eslint-config-two/index.js": "module.exports = { env: { node: true } }",
-            "node_modules/eslint-plugin-invalid-parser/index.js": "exports.configs = { foo: { parser: 'nonexistent-parser' } }",
-            "node_modules/eslint-plugin-invalid-config/index.js": "exports.configs = { foo: {} }",
-            "js/.eslintrc.js": "module.exports = { rules: { semi: [2, 'always'] } };",
-            "cjs/.eslintrc.cjs": "module.exports = { rules: { semi: [2, 'always'] } };",
-            "json/.eslintrc.json": "{ \"rules\": { \"quotes\": [2, \"double\"] } }",
-            "package-json/package.json": "{ \"eslintConfig\": { \"env\": { \"es6\": true } } }",
-            "yaml/.eslintrc.yaml": "env:\n    browser: true"
-        };
-        const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({ files });
-        const factory = new ConfigArrayFactory();
-
-        /**
-         * Apply `extends` property.
-         * @param {Object} configData The config that has `extends` property.
-         * @param {string} [filePath] The path to the config data.
-         * @returns {Object} The applied config data.
-         */
-        function applyExtends(configData, filePath = "whatever") {
-            return factory
-                .create(configData, { filePath })
-                .extractConfig(filePath)
-                .toCompatibleObjectAsConfigFileContent();
-        }
-
-        it("should apply extension 'foo' when specified from root directory config", () => {
-            const config = applyExtends({
-                extends: "foo",
-                rules: { eqeqeq: 2 }
-            });
-
-            assertConfig(config, {
-                env: { browser: true },
-                rules: { eqeqeq: [2] }
-            });
-        });
-
-        it("should apply all rules when extends config includes 'eslint:all'", () => {
-            const config = applyExtends({
-                extends: "eslint:all"
-            });
-
-            assert.strictEqual(config.rules.eqeqeq[0], "error");
-            assert.strictEqual(config.rules.curly[0], "error");
-        });
-
-        it("should throw an error when extends config module is not found", () => {
-            assert.throws(() => {
-                applyExtends({
-                    extends: "not-exist",
-                    rules: { eqeqeq: 2 }
-                });
-            }, /Failed to load config "not-exist" to extend from./u);
-        });
-
-        it("should throw an error when an eslint config is not found", () => {
-            assert.throws(() => {
-                applyExtends({
-                    extends: "eslint:foo",
-                    rules: { eqeqeq: 2 }
-                });
-            }, /Failed to load config "eslint:foo" to extend from./u);
-        });
-
-        it("should throw an error when a parser in a plugin config is not found", () => {
-            assert.throws(() => {
-                applyExtends({
-                    extends: "plugin:invalid-parser/foo",
-                    rules: { eqeqeq: 2 }
-                });
-            }, /Failed to load parser 'nonexistent-parser' declared in 'whatever » plugin:invalid-parser\/foo'/u);
-        });
-
-        it("should fall back to default parser when a parser called 'espree' is not found", () => {
-            const config = applyExtends({ parser: "espree" });
-
-            assertConfig(config, {
-                parser: require.resolve("espree")
-            });
-        });
-
-        it("should throw an error when a plugin config is not found", () => {
-            assert.throws(() => {
-                applyExtends({
-                    extends: "plugin:invalid-config/bar",
-                    rules: { eqeqeq: 2 }
-                });
-            }, /Failed to load config "plugin:invalid-config\/bar" to extend from./u);
-        });
-
-        it("should throw an error with a message template when a plugin referenced for a plugin config is not found", () => {
-            try {
-                applyExtends({
-                    extends: "plugin:nonexistent-plugin/baz",
-                    rules: { eqeqeq: 2 }
-                });
-            } catch (err) {
-                assert.strictEqual(err.messageTemplate, "plugin-missing");
-                assert.deepStrictEqual(err.messageData, {
-                    pluginName: "eslint-plugin-nonexistent-plugin",
-                    resolvePluginsRelativeTo: process.cwd(),
-                    importerName: "whatever"
-                });
-                return;
-            }
-            assert.fail("Expected to throw an error");
-        });
-
-        it("should throw an error with a message template when a plugin in the plugins list is not found", () => {
-            try {
-                applyExtends({
-                    plugins: ["nonexistent-plugin"]
-                });
-            } catch (err) {
-                assert.strictEqual(err.messageTemplate, "plugin-missing");
-                assert.deepStrictEqual(err.messageData, {
-                    pluginName: "eslint-plugin-nonexistent-plugin",
-                    resolvePluginsRelativeTo: process.cwd(),
-                    importerName: "whatever"
-                });
-                return;
-            }
-            assert.fail("Expected to throw an error");
-        });
-
-        it("should apply extensions recursively when specified from package", () => {
-            const config = applyExtends({
-                extends: "one",
-                rules: { eqeqeq: 2 }
-            });
-
-            assertConfig(config, {
-                env: { browser: true, node: true },
-                rules: { eqeqeq: [2] }
-            });
-        });
-
-        it("should apply extensions when specified from a JavaScript file", () => {
-            const config = applyExtends({
-                extends: ".eslintrc.js",
-                rules: { eqeqeq: 2 }
-            }, "js/foo.js");
-
-            assertConfig(config, {
-                rules: {
-                    semi: [2, "always"],
-                    eqeqeq: [2]
-                }
-            });
-        });
-
-        it("should apply extensions when specified from a YAML file", () => {
-            const config = applyExtends({
-                extends: ".eslintrc.yaml",
-                rules: { eqeqeq: 2 }
-            }, "yaml/foo.js");
-
-            assertConfig(config, {
-                env: { browser: true },
-                rules: {
-                    eqeqeq: [2]
-                }
-            });
-        });
-
-        it("should apply extensions when specified from a JSON file", () => {
-            const config = applyExtends({
-                extends: ".eslintrc.json",
-                rules: { eqeqeq: 2 }
-            }, "json/foo.js");
-
-            assertConfig(config, {
-                rules: {
-                    eqeqeq: [2],
-                    quotes: [2, "double"]
-                }
-            });
-        });
-
-        it("should apply extensions when specified from a package.json file in a sibling directory", () => {
-            const config = applyExtends({
-                extends: "../package-json/package.json",
-                rules: { eqeqeq: 2 }
-            }, "json/foo.js");
-
-            assertConfig(config, {
-                env: { es6: true },
-                rules: {
-                    eqeqeq: [2]
-                }
-            });
-        });
-    });
-
-    // This group moved from 'tests/lib/config/config-file.js' when refactoring to keep the cumulated test cases.
-    describe("loading config files should work properly.", () => {
-
-        /**
-         * Load a given config file.
-         * @param {ConfigArrayFactory} factory The factory to load.
-         * @param {string} filePath The path to a config file.
-         * @returns {Object} The applied config data.
-         */
-        function load(factory, filePath) {
-            return factory
-                .loadFile(filePath)
-                .extractConfig(filePath)
-                .toCompatibleObjectAsConfigFileContent();
-        }
-
-        it("should throw error if file doesn't exist", () => {
-            const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem();
-            const factory = new ConfigArrayFactory();
-
-            assert.throws(() => {
-                load(factory, "legacy/nofile.js");
-            });
-
-            assert.throws(() => {
-                load(factory, "legacy/package.json");
-            });
-        });
-
-        it("should load information from a legacy file", () => {
-            const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                files: {
-                    "legacy/.eslintrc": "{ rules: { eqeqeq: 2 } }"
-                }
-            });
-            const factory = new ConfigArrayFactory();
-            const config = load(factory, "legacy/.eslintrc");
-
-            assertConfig(config, {
-                rules: {
-                    eqeqeq: [2]
-                }
-            });
-        });
-
-        it("should load information from a JavaScript file", () => {
-            const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                files: {
-                    "js/.eslintrc.js": "module.exports = { rules: { semi: [2, 'always'] } };"
-                }
-            });
-            const factory = new ConfigArrayFactory();
-            const config = load(factory, "js/.eslintrc.js");
-
-            assertConfig(config, {
-                rules: {
-                    semi: [2, "always"]
-                }
-            });
-        });
-
-        it("should load information from a JavaScript file with a .cjs extension", () => {
-            const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                files: {
-                    "cjs/.eslintrc.cjs": "module.exports = { rules: { semi: [2, 'always'] } };"
-                }
-            });
-            const factory = new ConfigArrayFactory();
-            const config = load(factory, "cjs/.eslintrc.cjs");
-
-            assertConfig(config, {
-                rules: {
-                    semi: [2, "always"]
-                }
-            });
-        });
-
-        it("should throw error when loading invalid JavaScript file", () => {
-            const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                files: {
-                    "js/.eslintrc.broken.js": "module.exports = { rules: { semi: [2, 'always'] }"
-                }
-            });
-            const factory = new ConfigArrayFactory();
-
-            assert.throws(() => {
-                load(factory, "js/.eslintrc.broken.js");
-            }, /Cannot read config file/u);
-        });
-
-        it("should interpret parser module name when present in a JavaScript file", () => {
-            const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                files: {
-                    "node_modules/foo/index.js": "",
-                    "js/node_modules/foo/index.js": "",
-                    "js/.eslintrc.parser.js": `module.exports = {
-                        parser: 'foo',
-                        rules: { semi: [2, 'always'] }
-                    };`
-                }
-            });
-            const factory = new ConfigArrayFactory();
-            const config = load(factory, "js/.eslintrc.parser.js");
-
-            assertConfig(config, {
-                parser: path.resolve("js/node_modules/foo/index.js"),
-                rules: {
-                    semi: [2, "always"]
-                }
-            });
-        });
-
-        it("should interpret parser path when present in a JavaScript file", () => {
-            const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                files: {
-                    "js/.eslintrc.parser2.js": `module.exports = {
-                        parser: './not-a-config.js',
-                        rules: { semi: [2, 'always'] }
-                    };`,
-                    "js/not-a-config.js": ""
-                }
-            });
-            const factory = new ConfigArrayFactory();
-            const config = load(factory, "js/.eslintrc.parser2.js");
-
-            assertConfig(config, {
-                parser: path.resolve("js/not-a-config.js"),
-                rules: {
-                    semi: [2, "always"]
-                }
-            });
-        });
-
-        it("should interpret parser module name or path when parser is set to default parser in a JavaScript file", () => {
-            const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                files: {
-                    "js/.eslintrc.parser3.js": `module.exports = {
-                        parser: 'espree',
-                        rules: { semi: [2, 'always'] }
-                    };`
-                }
-            });
-            const factory = new ConfigArrayFactory();
-            const config = load(factory, "js/.eslintrc.parser3.js");
-
-            assertConfig(config, {
-                parser: require.resolve("espree"),
-                rules: {
-                    semi: [2, "always"]
-                }
-            });
-        });
-
-        it("should load information from a JSON file", () => {
-            const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                files: {
-                    "json/.eslintrc.json": "{ \"rules\": { \"quotes\": [2, \"double\"] } }"
-                }
-            });
-            const factory = new ConfigArrayFactory();
-            const config = load(factory, "json/.eslintrc.json");
-
-            assertConfig(config, {
-                rules: {
-                    quotes: [2, "double"]
-                }
-            });
-        });
-
-        it("should load fresh information from a JSON file", () => {
-            const { fs, ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem();
-            const factory = new ConfigArrayFactory();
-            const initialConfig = {
-                rules: {
-                    quotes: [2, "double"]
-                }
-            };
-            const updatedConfig = {
-                rules: {
-                    quotes: [0]
-                }
-            };
-            let config;
-
-            fs.writeFileSync("fresh-test.json", JSON.stringify(initialConfig));
-            config = load(factory, "fresh-test.json");
-            assertConfig(config, initialConfig);
-
-            fs.writeFileSync("fresh-test.json", JSON.stringify(updatedConfig));
-            config = load(factory, "fresh-test.json");
-            assertConfig(config, updatedConfig);
-        });
-
-        it("should load information from a package.json file", () => {
-            const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                files: {
-                    "package-json/package.json": "{ \"eslintConfig\": { \"env\": { \"es6\": true } } }"
-                }
-            });
-            const factory = new ConfigArrayFactory();
-            const config = load(factory, "package-json/package.json");
-
-            assertConfig(config, {
-                env: { es6: true }
-            });
-        });
-
-        it("should throw error when loading invalid package.json file", () => {
-            const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                files: {
-                    "broken-package-json/package.json": "{ \"eslintConfig\": { \"env\": { \"es6\": true } }"
-                }
-            });
-            const factory = new ConfigArrayFactory();
-
-            assert.throws(() => {
-                try {
-                    load(factory, "broken-package-json/package.json");
-                } catch (error) {
-                    assert.strictEqual(error.messageTemplate, "failed-to-read-json");
-                    throw error;
-                }
-            }, /Cannot read config file/u);
-        });
-
-        it("should load fresh information from a package.json file", () => {
-            const { fs, ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem();
-            const factory = new ConfigArrayFactory();
-            const initialConfig = {
-                eslintConfig: {
-                    rules: {
-                        quotes: [2, "double"]
-                    }
-                }
-            };
-            const updatedConfig = {
-                eslintConfig: {
-                    rules: {
-                        quotes: [0]
-                    }
-                }
-            };
-            let config;
-
-            fs.writeFileSync("package.json", JSON.stringify(initialConfig));
-            config = load(factory, "package.json");
-            assertConfig(config, initialConfig.eslintConfig);
-
-            fs.writeFileSync("package.json", JSON.stringify(updatedConfig));
-            config = load(factory, "package.json");
-            assertConfig(config, updatedConfig.eslintConfig);
-        });
-
-        it("should load fresh information from a .eslintrc.js file", () => {
-            const { fs, ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem();
-            const factory = new ConfigArrayFactory();
-            const initialConfig = {
-                rules: {
-                    quotes: [2, "double"]
-                }
-            };
-            const updatedConfig = {
-                rules: {
-                    quotes: [0]
-                }
-            };
-            let config;
-
-            fs.writeFileSync(".eslintrc.js", `module.exports = ${JSON.stringify(initialConfig)}`);
-            config = load(factory, ".eslintrc.js");
-            assertConfig(config, initialConfig);
-
-            fs.writeFileSync(".eslintrc.js", `module.exports = ${JSON.stringify(updatedConfig)}`);
-            config = load(factory, ".eslintrc.js");
-            assertConfig(config, updatedConfig);
-        });
-
-        it("should load information from a YAML file", () => {
-            const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                files: {
-                    "yaml/.eslintrc.yaml": "env:\n    browser: true"
-                }
-            });
-            const factory = new ConfigArrayFactory();
-            const config = load(factory, "yaml/.eslintrc.yaml");
-
-            assertConfig(config, {
-                env: { browser: true }
-            });
-        });
-
-        it("should load information from an empty YAML file", () => {
-            const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                files: {
-                    "yaml/.eslintrc.empty.yaml": "{}"
-                }
-            });
-            const factory = new ConfigArrayFactory();
-            const config = load(factory, "yaml/.eslintrc.empty.yaml");
-
-            assertConfig(config, {});
-        });
-
-        it("should load information from a YML file", () => {
-            const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                files: {
-                    "yml/.eslintrc.yml": "env:\n    node: true"
-                }
-            });
-            const factory = new ConfigArrayFactory();
-            const config = load(factory, "yml/.eslintrc.yml");
-
-            assertConfig(config, {
-                env: { node: true }
-            });
-        });
-
-        it("should load information from a YML file and apply extensions", () => {
-            const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                files: {
-                    "extends/.eslintrc.yml": "extends: ../package-json/package.json\nrules:\n    booya: 2",
-                    "package-json/package.json": "{ \"eslintConfig\": { \"env\": { \"es6\": true } } }"
-                }
-            });
-            const factory = new ConfigArrayFactory();
-            const config = load(factory, "extends/.eslintrc.yml");
-
-            assertConfig(config, {
-                env: { es6: true },
-                rules: { booya: [2] }
-            });
-        });
-
-        it("should load information from `extends` chain.", () => {
-            const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                files: {
-                    "extends-chain": {
-                        "node_modules/eslint-config-a": {
-                            "node_modules/eslint-config-b": {
-                                "node_modules/eslint-config-c": {
-                                    "index.js": "module.exports = { rules: { c: 2 } };"
-                                },
-                                "index.js": "module.exports = { extends: 'c', rules: { b: 2 } };"
-                            },
-                            "index.js": "module.exports = { extends: 'b', rules: { a: 2 } };"
-                        },
-                        ".eslintrc.json": "{ \"extends\": \"a\" }"
-                    }
-                }
-            });
-            const factory = new ConfigArrayFactory();
-            const config = load(factory, "extends-chain/.eslintrc.json");
-
-            assertConfig(config, {
-                rules: {
-                    a: [2], // from node_modules/eslint-config-a
-                    b: [2], // from node_modules/eslint-config-a/node_modules/eslint-config-b
-                    c: [2] // from node_modules/eslint-config-a/node_modules/eslint-config-b/node_modules/eslint-config-c
-                }
-            });
-        });
-
-        it("should load information from `extends` chain with relative path.", () => {
-            const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                files: {
-                    "extends-chain-2": {
-                        "node_modules/eslint-config-a/index.js": "module.exports = { extends: './relative.js', rules: { a: 2 } };",
-                        "node_modules/eslint-config-a/relative.js": "module.exports = { rules: { relative: 2 } };",
-                        ".eslintrc.json": "{ \"extends\": \"a\" }"
-                    }
-                }
-            });
-            const factory = new ConfigArrayFactory();
-            const config = load(factory, "extends-chain-2/.eslintrc.json");
-
-            assertConfig(config, {
-                rules: {
-                    a: [2], // from node_modules/eslint-config-a/index.js
-                    relative: [2] // from node_modules/eslint-config-a/relative.js
-                }
-            });
-        });
-
-        it("should load information from `extends` chain in .eslintrc with relative path.", () => {
-            const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                files: {
-                    "extends-chain-2": {
-                        "node_modules/eslint-config-a/index.js": "module.exports = { extends: './relative.js', rules: { a: 2 } };",
-                        "node_modules/eslint-config-a/relative.js": "module.exports = { rules: { relative: 2 } };",
-                        "relative.eslintrc.json": "{ \"extends\": \"./node_modules/eslint-config-a/index.js\" }"
-                    }
-                }
-            });
-            const factory = new ConfigArrayFactory();
-            const config = load(factory, "extends-chain-2/relative.eslintrc.json");
-
-            assertConfig(config, {
-                rules: {
-                    a: [2], // from node_modules/eslint-config-a/index.js
-                    relative: [2] // from node_modules/eslint-config-a/relative.js
-                }
-            });
-        });
-
-        it("should load information from `parser` in .eslintrc with relative path.", () => {
-            const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                files: {
-                    "extends-chain-2": {
-                        "parser.eslintrc.json": "{ \"parser\": \"./parser.js\" }",
-                        "parser.js": ""
-                    }
-                }
-            });
-            const factory = new ConfigArrayFactory();
-            const config = load(factory, "extends-chain-2/parser.eslintrc.json");
-
-            assertConfig(config, {
-                parser: path.resolve("extends-chain-2/parser.js")
-            });
-        });
-
-        describe("Plugins", () => {
-            it("should load information from a YML file and load plugins", () => {
-                const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                    files: {
-                        "node_modules/eslint-plugin-test/index.js": `
-                            module.exports = {
-                                environments: {
-                                    bar: { globals: { bar: true } }
-                                }
-                            }
-                        `,
-                        "plugins/.eslintrc.yml": `
-                            plugins:
-                                - test
-                            rules:
-                                test/foo: 2
-                            env:
-                                test/bar: true
-                        `
-                    }
-                });
-                const factory = new ConfigArrayFactory();
-                const config = load(factory, "plugins/.eslintrc.yml");
-
-                assertConfig(config, {
-                    env: { "test/bar": true },
-                    plugins: ["test"],
-                    rules: {
-                        "test/foo": [2]
-                    }
-                });
-            });
-
-            it("should load two separate configs from a plugin", () => {
-                const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                    files: {
-                        "node_modules/eslint-plugin-test/index.js": `
-                            module.exports = {
-                                configs: {
-                                    foo: { rules: { semi: 2, quotes: 1 } },
-                                    bar: { rules: { quotes: 2, yoda: 2 } }
-                                }
-                            }
-                        `,
-                        "plugins/.eslintrc.yml": `
-                            extends:
-                                - plugin:test/foo
-                                - plugin:test/bar
-                        `
-                    }
-                });
-                const factory = new ConfigArrayFactory();
-                const config = load(factory, "plugins/.eslintrc.yml");
-
-                assertConfig(config, {
-                    rules: {
-                        semi: [2],
-                        quotes: [2],
-                        yoda: [2]
-                    }
-                });
-            });
-        });
-
-        describe("even if config files have Unicode BOM,", () => {
-            it("should read the JSON config file correctly.", () => {
-                const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                    files: {
-                        "bom/.eslintrc.json": "\uFEFF{ \"rules\": { \"semi\": \"error\" } }"
-                    }
-                });
-                const factory = new ConfigArrayFactory();
-                const config = load(factory, "bom/.eslintrc.json");
-
-                assertConfig(config, {
-                    rules: {
-                        semi: ["error"]
-                    }
-                });
-            });
-
-            it("should read the YAML config file correctly.", () => {
-                const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                    files: {
-                        "bom/.eslintrc.yaml": "\uFEFFrules:\n  semi: error"
-                    }
-                });
-                const factory = new ConfigArrayFactory();
-                const config = load(factory, "bom/.eslintrc.yaml");
-
-                assertConfig(config, {
-                    rules: {
-                        semi: ["error"]
-                    }
-                });
-            });
-
-            it("should read the config in package.json correctly.", () => {
-                const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                    files: {
-                        "bom/package.json": "\uFEFF{ \"eslintConfig\": { \"rules\": { \"semi\": \"error\" } } }"
-                    }
-                });
-                const factory = new ConfigArrayFactory();
-                const config = load(factory, "bom/package.json");
-
-                assertConfig(config, {
-                    rules: {
-                        semi: ["error"]
-                    }
-                });
-            });
-        });
-
-        it("throws an error including the config file name if the config file is invalid", () => {
-            const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                files: {
-                    "invalid/invalid-top-level-property.yml": "invalidProperty: 3"
-                }
-            });
-            const factory = new ConfigArrayFactory();
-
-            try {
-                load(factory, "invalid/invalid-top-level-property.yml");
-            } catch (err) {
-                assert.include(err.message, `ESLint configuration in ${`invalid${path.sep}invalid-top-level-property.yml`} is invalid`);
-                return;
-            }
-            assert.fail();
-        });
-    });
-
-    // This group moved from 'tests/lib/config/config-file.js' when refactoring to keep the cumulated test cases.
-    describe("'extends' property should resolve the location of configs properly.", () => {
-        const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-            cwd: () => tempDir,
-            files: {
-                "node_modules/eslint-config-foo/index.js": "",
-                "node_modules/eslint-config-foo/bar.js": "",
-                "node_modules/eslint-config-eslint-configfoo/index.js": "",
-                "node_modules/@foo/eslint-config/index.js": "",
-                "node_modules/@foo/eslint-config-bar/index.js": "",
-                "node_modules/eslint-plugin-foo/index.js": "exports.configs = { bar: {} }",
-                "node_modules/@foo/eslint-plugin/index.js": "exports.configs = { bar: {} }",
-                "node_modules/@foo/eslint-plugin-bar/index.js": "exports.configs = { baz: {} }",
-                "foo/bar/.eslintrc": "",
-                ".eslintrc": ""
-            }
-        });
-        const factory = new ConfigArrayFactory();
-
-        /**
-         * Resolve `extends` module.
-         * @param {string} request The module name to resolve.
-         * @param {string} [relativeTo] The importer path to resolve.
-         * @returns {string} The resolved path.
-         */
-        function resolve(request, relativeTo) {
-            return factory.create(
-                { extends: request },
-                { filePath: relativeTo }
-            )[0];
-        }
-
-        describe("Relative to CWD", () => {
-            for (const { input, expected } of [
-                { input: ".eslintrc", expected: path.resolve(tempDir, ".eslintrc") },
-                { input: "eslint-config-foo", expected: path.resolve(tempDir, "node_modules/eslint-config-foo/index.js") },
-                { input: "eslint-config-foo/bar", expected: path.resolve(tempDir, "node_modules/eslint-config-foo/bar.js") },
-                { input: "foo", expected: path.resolve(tempDir, "node_modules/eslint-config-foo/index.js") },
-                { input: "foo/bar", expected: path.resolve(tempDir, "node_modules/eslint-config-foo/bar.js") },
-                { input: "eslint-configfoo", expected: path.resolve(tempDir, "node_modules/eslint-config-eslint-configfoo/index.js") },
-                { input: "@foo/eslint-config", expected: path.resolve(tempDir, "node_modules/@foo/eslint-config/index.js") },
-                { input: "@foo", expected: path.resolve(tempDir, "node_modules/@foo/eslint-config/index.js") },
-                { input: "@foo/bar", expected: path.resolve(tempDir, "node_modules/@foo/eslint-config-bar/index.js") },
-                { input: "plugin:foo/bar", expected: path.resolve(tempDir, "node_modules/eslint-plugin-foo/index.js") },
-                { input: "plugin:@foo/bar", expected: path.resolve(tempDir, "node_modules/@foo/eslint-plugin/index.js") },
-                { input: "plugin:@foo/bar/baz", expected: path.resolve(tempDir, "node_modules/@foo/eslint-plugin-bar/index.js") }
-            ]) {
-                it(`should return ${expected} when passed ${input}`, () => {
-                    const result = resolve(input);
-
-                    assert.strictEqual(result.filePath, expected);
-                });
-            }
-        });
-
-        describe("Relative to config file", () => {
-            const relativePath = path.resolve(tempDir, "./foo/bar/.eslintrc");
-
-            for (const { input, expected } of [
-                { input: ".eslintrc", expected: path.join(path.dirname(relativePath), ".eslintrc") },
-                { input: "eslint-config-foo", expected: path.resolve(tempDir, "node_modules/eslint-config-foo/index.js") },
-                { input: "eslint-config-foo/bar", expected: path.resolve(tempDir, "node_modules/eslint-config-foo/bar.js") },
-                { input: "foo", expected: path.resolve(tempDir, "node_modules/eslint-config-foo/index.js") },
-                { input: "foo/bar", expected: path.resolve(tempDir, "node_modules/eslint-config-foo/bar.js") },
-                { input: "eslint-configfoo", expected: path.resolve(tempDir, "node_modules/eslint-config-eslint-configfoo/index.js") },
-                { input: "@foo/eslint-config", expected: path.resolve(tempDir, "node_modules/@foo/eslint-config/index.js") },
-                { input: "@foo", expected: path.resolve(tempDir, "node_modules/@foo/eslint-config/index.js") },
-                { input: "@foo/bar", expected: path.resolve(tempDir, "node_modules/@foo/eslint-config-bar/index.js") },
-                { input: "plugin:foo/bar", expected: path.resolve(tempDir, "node_modules/eslint-plugin-foo/index.js") },
-                { input: "plugin:@foo/bar", expected: path.resolve(tempDir, "node_modules/@foo/eslint-plugin/index.js") },
-                { input: "plugin:@foo/bar/baz", expected: path.resolve(tempDir, "node_modules/@foo/eslint-plugin-bar/index.js") }
-            ]) {
-                it(`should return ${expected} when passed ${input}`, () => {
-                    const result = resolve(input, relativePath);
-
-                    assert.strictEqual(result.filePath, expected);
-                });
-            }
-        });
-    });
-
-    // This group moved from 'tests/lib/config/plugins.js' when refactoring to keep the cumulated test cases.
-    describe("'plugins' property should load a correct plugin.", () => {
-        const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-            cwd: () => tempDir,
-            files: {
-                "node_modules/@scope/eslint-plugin-example/index.js": "exports.configs = { name: '@scope/eslint-plugin-example' };",
-                "node_modules/eslint-plugin-example/index.js": "exports.configs = { name: 'eslint-plugin-example' };",
-                "node_modules/eslint-plugin-throws-on-load/index.js": "throw new Error('error thrown while loading this module')"
-            }
-        });
-        const factory = new ConfigArrayFactory();
-
-        /**
-         * Load a plugin.
-         * @param {string} request A request to load a plugin.
-         * @param {ConfigArrayFactory} [configArrayFactory] The factory to use
-         * @returns {Map<string,Object>} The loaded plugins.
-         */
-        function load(request, configArrayFactory = factory) {
-            const config = configArrayFactory.create({ plugins: [request] });
-
-            return new Map(
-                Object
-                    .entries(config[0].plugins)
-                    .map(([id, entry]) => {
-                        if (entry.error) {
-                            throw entry.error;
-                        }
-                        return [id, entry.definition];
-                    })
-            );
-        }
-
-        it("should load a plugin when referenced by short name", () => {
-            const loadedPlugins = load("example");
-
-            assertPluginDefinition(
-                loadedPlugins.get("example"),
-                { configs: { name: "eslint-plugin-example" } }
-            );
-        });
-
-        it("should load a plugin when referenced by short name, even when using a custom loadPluginsRelativeTo value", () => {
-            const { ConfigArrayFactory: FactoryWithPluginsInSubdir } = defineConfigArrayFactoryWithInMemoryFileSystem({
-                cwd: () => tempDir,
-                files: {
-                    "subdir/node_modules/eslint-plugin-example/index.js": "exports.configs = { name: 'eslint-plugin-example' };"
-                }
-            });
-
-            const factoryWithCustomPluginPath = new FactoryWithPluginsInSubdir({ resolvePluginsRelativeTo: "subdir" });
-
-            const loadedPlugins = load("example", factoryWithCustomPluginPath);
-
-            assertPluginDefinition(
-                loadedPlugins.get("example"),
-                { configs: { name: "eslint-plugin-example" } }
-            );
-        });
-
-        it("should load a plugin when referenced by long name", () => {
-            const loadedPlugins = load("eslint-plugin-example");
-
-            assertPluginDefinition(
-                loadedPlugins.get("example"),
-                { configs: { name: "eslint-plugin-example" } }
-            );
-        });
-
-        it("should throw an error when a plugin has whitespace", () => {
-            assert.throws(() => {
-                load("whitespace ");
-            }, /Whitespace found in plugin name 'whitespace '/u);
-            assert.throws(() => {
-                load("whitespace\t");
-            }, /Whitespace found in plugin name/u);
-            assert.throws(() => {
-                load("whitespace\n");
-            }, /Whitespace found in plugin name/u);
-            assert.throws(() => {
-                load("whitespace\r");
-            }, /Whitespace found in plugin name/u);
-        });
-
-        it("should throw an error when a plugin doesn't exist", () => {
-            assert.throws(() => {
-                load("nonexistentplugin");
-            }, /Failed to load plugin/u);
-        });
-
-        it("should rethrow an error that a plugin throws on load", () => {
-            assert.throws(() => {
-                load("throws-on-load");
-            }, /error thrown while loading this module/u);
-        });
-
-        it("should load a scoped plugin when referenced by short name", () => {
-            const loadedPlugins = load("@scope/example");
-
-            assertPluginDefinition(
-                loadedPlugins.get("@scope/example"),
-                { configs: { name: "@scope/eslint-plugin-example" } }
-            );
-        });
-
-        it("should load a scoped plugin when referenced by long name", () => {
-            const loadedPlugins = load("@scope/eslint-plugin-example");
-
-            assertPluginDefinition(
-                loadedPlugins.get("@scope/example"),
-                { configs: { name: "@scope/eslint-plugin-example" } }
-            );
-        });
-
-        describe("when referencing a scope plugin and omitting @scope/", () => {
-            it("should load a scoped plugin when referenced by short name, but should not get the plugin if '@scope/' is omitted", () => {
-                const loadedPlugins = load("@scope/example");
-
-                assert.strictEqual(loadedPlugins.get("example"), void 0);
-            });
-
-            it("should load a scoped plugin when referenced by long name, but should not get the plugin if '@scope/' is omitted", () => {
-                const loadedPlugins = load("@scope/eslint-plugin-example");
-
-                assert.strictEqual(loadedPlugins.get("example"), void 0);
-            });
-        });
-    });
-
-    // This group moved from 'tests/lib/config/plugins.js' when refactoring to keep the cumulated test cases.
-    describe("'plugins' property should load some correct plugins.", () => {
-        const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({
-            cwd: () => tempDir,
-            files: {
-                "node_modules/eslint-plugin-example1/index.js": "exports.configs = { name: 'eslint-plugin-example1' };",
-                "node_modules/eslint-plugin-example2/index.js": "exports.configs = { name: 'eslint-plugin-example2' };"
-            }
-        });
-        const factory = new ConfigArrayFactory();
-
-        /**
-         * Load a plugin.
-         * @param {string[]} request A request to load a plugin.
-         * @returns {Map<string,Object>} The loaded plugins.
-         */
-        function loadAll(request) {
-            const config = factory.create({ plugins: request });
-
-            return new Map(
-                Object
-                    .entries(config[0].plugins)
-                    .map(([id, entry]) => {
-                        if (entry.error) {
-                            throw entry.error;
-                        }
-                        return [id, entry.definition];
-                    })
-            );
-        }
-
-        it("should load plugins when passed multiple plugins", () => {
-            const loadedPlugins = loadAll(["example1", "example2"]);
-
-            assertPluginDefinition(
-                loadedPlugins.get("example1"),
-                { configs: { name: "eslint-plugin-example1" } }
-            );
-            assertPluginDefinition(
-                loadedPlugins.get("example2"),
-                { configs: { name: "eslint-plugin-example2" } }
-            );
-        });
-    });
-});
diff --git a/eslint/tests/lib/cli-engine/config-array/config-array.js b/eslint/tests/lib/cli-engine/config-array/config-array.js
deleted file mode 100644 (file)
index fafb688..0000000
+++ /dev/null
@@ -1,739 +0,0 @@
-/**
- * @fileoverview Tests for ConfigArray class.
- * @author Toru Nagashima <https://github.com/mysticatea>
- */
-"use strict";
-
-const path = require("path");
-const { assert } = require("chai");
-const { ConfigArray, OverrideTester, getUsedExtractedConfigs } = require("../../../../lib/cli-engine/config-array");
-
-describe("ConfigArray", () => {
-    it("should be a sub class of Array.", () => {
-        assert(new ConfigArray() instanceof Array);
-    });
-
-    describe("'constructor(...elements)' should adopt the elements as array elements.", () => {
-        const patterns = [
-            { elements: [] },
-            { elements: [{ value: 1 }] },
-            { elements: [{ value: 2 }, { value: 3 }] },
-            { elements: [{ value: 4 }, { value: 5 }, { value: 6 }] }
-        ];
-
-        for (const { elements } of patterns) {
-            describe(`if it gave ${JSON.stringify(elements)} then`, () => {
-                let configArray;
-
-                beforeEach(() => {
-                    configArray = new ConfigArray(...elements);
-                });
-
-                it(`should have ${elements.length} as the length.`, () => {
-                    assert.strictEqual(configArray.length, elements.length);
-                });
-
-                for (let i = 0; i < elements.length; ++i) {
-                    it(`should have ${JSON.stringify(elements[i])} at configArray[${i}].`, () => { // eslint-disable-line no-loop-func
-                        assert.strictEqual(configArray[i], elements[i]);
-                    });
-                }
-            });
-        }
-    });
-
-    describe("'isRoot()' method should be the value of the last element which has 'root' property.", () => {
-        const patterns = [
-            { elements: [], expected: false },
-            { elements: [{}], expected: false },
-            { elements: [{}, {}], expected: false },
-            { elements: [{ root: false }], expected: false },
-            { elements: [{ root: true }], expected: true },
-            { elements: [{ root: true }, { root: false }], expected: false },
-            { elements: [{ root: false }, { root: true }], expected: true },
-            { elements: [{ root: false }, { root: true }, { rules: {} }], expected: true }, // ignore undefined.
-            { elements: [{ root: true }, { root: 1 }], expected: true } // ignore non-boolean value
-        ];
-
-        for (const { elements, expected } of patterns) {
-            it(`should be ${expected} if the elements are ${JSON.stringify(elements)}.`, () => {
-                assert.strictEqual(new ConfigArray(...elements).isRoot(), expected);
-            });
-        }
-    });
-
-    describe("'pluginEnvironments' property should be the environments of all plugins.", () => {
-        const env = {
-            "aaa/xxx": {},
-            "bbb/xxx": {}
-        };
-        let configArray;
-
-        beforeEach(() => {
-            configArray = new ConfigArray(
-                {
-                    plugins: {
-                        aaa: {
-                            definition: {
-                                environments: {
-                                    xxx: env["aaa/xxx"]
-                                }
-                            }
-                        }
-                    }
-                },
-                {
-                    criteria: OverrideTester.create(["*.ts"], [], process.cwd()),
-                    plugins: {
-                        bbb: {
-                            definition: {
-                                environments: {
-                                    xxx: env["bbb/xxx"]
-                                }
-                            }
-                        }
-                    }
-                }
-            );
-        });
-
-        it("should return null for built-in env", () => {
-            assert.strictEqual(configArray.pluginEnvironments.get("node"), void 0);
-        });
-
-        it("should return 'aaa/xxx' if it exists.", () => {
-            assert.strictEqual(configArray.pluginEnvironments.get("aaa/xxx"), env["aaa/xxx"]);
-        });
-
-        it("should return 'bbb/xxx' if it exists.", () => {
-            assert.strictEqual(configArray.pluginEnvironments.get("bbb/xxx"), env["bbb/xxx"]);
-        });
-
-        it("should throw an error if it tried to mutate.", () => {
-            assert.throws(() => {
-                configArray.pluginEnvironments.set("ccc/xxx", {});
-            });
-        });
-    });
-
-    describe("'pluginProcessors' property should be the processors of all plugins.", () => {
-        const processors = {
-            "aaa/.xxx": {},
-            "bbb/.xxx": {}
-        };
-        let configArray;
-
-        beforeEach(() => {
-            configArray = new ConfigArray(
-                {
-                    plugins: {
-                        aaa: {
-                            definition: {
-                                processors: {
-                                    ".xxx": processors["aaa/.xxx"]
-                                }
-                            }
-                        }
-                    }
-                },
-                {
-                    criteria: OverrideTester.create(["*.ts"], [], process.cwd()),
-                    plugins: {
-                        bbb: {
-                            definition: {
-                                processors: {
-                                    ".xxx": processors["bbb/.xxx"]
-                                }
-                            }
-                        }
-                    }
-                }
-            );
-        });
-
-        it("should return 'aaa/.xxx' if it exists.", () => {
-            assert.strictEqual(configArray.pluginProcessors.get("aaa/.xxx"), processors["aaa/.xxx"]);
-        });
-
-        it("should return 'bbb/.xxx' if it exists.", () => {
-            assert.strictEqual(configArray.pluginProcessors.get("bbb/.xxx"), processors["bbb/.xxx"]);
-        });
-
-        it("should throw an error if it tried to mutate.", () => {
-            assert.throws(() => {
-                configArray.pluginProcessors.set("ccc/.xxx", {});
-            });
-        });
-    });
-
-    describe("'pluginRules' property should be the rules of all plugins.", () => {
-        const rules = {
-            "aaa/xxx": {},
-            "bbb/xxx": {}
-        };
-        let configArray;
-
-        beforeEach(() => {
-            configArray = new ConfigArray(
-                {
-                    plugins: {
-                        aaa: {
-                            definition: {
-                                rules: {
-                                    xxx: rules["aaa/xxx"]
-                                }
-                            }
-                        }
-                    }
-                },
-                {
-                    criteria: OverrideTester.create(["*.ts"], [], process.cwd()),
-                    plugins: {
-                        bbb: {
-                            definition: {
-                                rules: {
-                                    xxx: rules["bbb/xxx"]
-                                }
-                            }
-                        }
-                    }
-                }
-            );
-        });
-
-        it("should return null for built-in rules", () => {
-            assert.strictEqual(configArray.pluginRules.get("eqeqeq"), void 0);
-        });
-
-        it("should return 'aaa/xxx' if it exists.", () => {
-            assert.strictEqual(configArray.pluginRules.get("aaa/xxx"), rules["aaa/xxx"]);
-        });
-
-        it("should return 'bbb/xxx' if it exists.", () => {
-            assert.strictEqual(configArray.pluginRules.get("bbb/xxx"), rules["bbb/xxx"]);
-        });
-
-        it("should throw an error if it tried to mutate.", () => {
-            assert.throws(() => {
-                configArray.pluginRules.set("ccc/xxx", {});
-            });
-        });
-    });
-
-    describe("'extractConfig(filePath)' method should retrieve the merged config for a given file.", () => {
-        it("should throw an error if a 'parser' has the loading error.", () => {
-            assert.throws(() => {
-                new ConfigArray(
-                    {
-                        parser: { error: new Error("Failed to load a parser.") }
-                    }
-                ).extractConfig(__filename);
-            }, "Failed to load a parser.");
-        });
-
-        it("should not throw if the errored 'parser' was not used; overwriten", () => {
-            const parser = { id: "a parser" };
-            const config = new ConfigArray(
-                {
-                    parser: { error: new Error("Failed to load a parser.") }
-                },
-                {
-                    parser
-                }
-            ).extractConfig(__filename);
-
-            assert.strictEqual(config.parser, parser);
-        });
-
-        it("should not throw if the errored 'parser' was not used; not matched", () => {
-            const config = new ConfigArray(
-                {
-                    criteria: OverrideTester.create(["*.ts"], [], process.cwd()),
-                    parser: { error: new Error("Failed to load a parser.") }
-                }
-            ).extractConfig(__filename);
-
-            assert.strictEqual(config.parser, null);
-        });
-
-        it("should throw an error if a 'plugins' value has the loading error.", () => {
-            assert.throws(() => {
-                new ConfigArray(
-                    {
-                        plugins: {
-                            foo: { error: new Error("Failed to load a plugin.") }
-                        }
-                    }
-                ).extractConfig(__filename);
-            }, "Failed to load a plugin.");
-        });
-
-        it("should not throw if the errored 'plugins' value was not used; not matched", () => {
-            const config = new ConfigArray(
-                {
-                    criteria: OverrideTester.create(["*.ts"], [], process.cwd()),
-                    plugins: {
-                        foo: { error: new Error("Failed to load a plugin.") }
-                    }
-                }
-            ).extractConfig(__filename);
-
-            assert.deepStrictEqual(config.plugins, {});
-        });
-
-        it("should not merge the elements which were not matched.", () => {
-            const config = new ConfigArray(
-                {
-                    rules: {
-                        "no-redeclare": "error"
-                    }
-                },
-                {
-                    criteria: OverrideTester.create(["*.js"], [], process.cwd()),
-                    rules: {
-                        "no-undef": "error"
-                    }
-                },
-                {
-                    criteria: OverrideTester.create(["*.js"], [path.basename(__filename)], process.cwd()),
-                    rules: {
-                        "no-use-before-define": "error"
-                    }
-                },
-                {
-                    criteria: OverrideTester.create(["*.ts"], [], process.cwd()),
-                    rules: {
-                        "no-unused-vars": "error"
-                    }
-                }
-            ).extractConfig(__filename);
-
-            assert.deepStrictEqual(config.rules, {
-                "no-redeclare": ["error"],
-                "no-undef": ["error"]
-            });
-        });
-
-        it("should return the same instance for every the same matching.", () => {
-            const configArray = new ConfigArray(
-                {
-                    rules: {
-                        "no-redeclare": "error"
-                    }
-                },
-                {
-                    criteria: OverrideTester.create(["*.js"], [], process.cwd()),
-                    rules: {
-                        "no-undef": "error"
-                    }
-                },
-                {
-                    criteria: OverrideTester.create(["*.js"], [path.basename(__filename)], process.cwd()),
-                    rules: {
-                        "no-use-before-define": "error"
-                    }
-                },
-                {
-                    criteria: OverrideTester.create(["*.ts"], [], process.cwd()),
-                    rules: {
-                        "no-unused-vars": "error"
-                    }
-                }
-            );
-
-            assert.strictEqual(
-                configArray.extractConfig(path.join(__dirname, "a.js")),
-                configArray.extractConfig(path.join(__dirname, "b.js"))
-            );
-        });
-
-        /**
-         * Merge two config data.
-         *
-         * The test cases which depend on this function were moved from
-         * 'tests/lib/config/config-ops.js' when refactoring to keep the
-         * cumulated test cases.
-         *
-         * Previously, the merging logic of multiple config data had been
-         * implemented in `ConfigOps.merge()` function. But currently, it's
-         * implemented in `ConfigArray#extractConfig()` method.
-         * @param {Object} target A config data.
-         * @param {Object} source Another config data.
-         * @returns {Object} The merged config data.
-         */
-        function merge(target, source) {
-            return new ConfigArray(target, source).extractConfig(__filename);
-        }
-
-        it("should combine two objects when passed two objects with different top-level properties", () => {
-            const config = [
-                { env: { browser: true } },
-                { globals: { foo: "bar" } }
-            ];
-
-            const result = merge(config[0], config[1]);
-
-            assert.strictEqual(result.globals.foo, "bar");
-            assert.isTrue(result.env.browser);
-        });
-
-        it("should combine without blowing up on null values", () => {
-            const config = [
-                { env: { browser: true } },
-                { env: { node: null } }
-            ];
-
-            const result = merge(config[0], config[1]);
-
-            assert.strictEqual(result.env.node, null);
-            assert.isTrue(result.env.browser);
-        });
-
-        it("should combine two objects with parser when passed two objects with different top-level properties", () => {
-            const config = [
-                { env: { browser: true }, parser: "espree" },
-                { globals: { foo: "bar" } }
-            ];
-
-            const result = merge(config[0], config[1]);
-
-            assert.strictEqual(result.parser, "espree");
-        });
-
-        it("should combine configs and override rules when passed configs with the same rules", () => {
-            const config = [
-                { rules: { "no-mixed-requires": [0, false] } },
-                { rules: { "no-mixed-requires": [1, true] } }
-            ];
-
-            const result = merge(config[0], config[1]);
-
-            assert.isArray(result.rules["no-mixed-requires"]);
-            assert.strictEqual(result.rules["no-mixed-requires"][0], 1);
-            assert.strictEqual(result.rules["no-mixed-requires"][1], true);
-        });
-
-        it("should combine configs when passed configs with parserOptions", () => {
-            const config = [
-                { parserOptions: { ecmaFeatures: { jsx: true } } },
-                { parserOptions: { ecmaFeatures: { globalReturn: true } } }
-            ];
-
-            const result = merge(config[0], config[1]);
-
-            assert.deepStrictEqual(result, {
-                configNameOfNoInlineConfig: "",
-                env: {},
-                globals: {},
-                ignores: void 0,
-                noInlineConfig: void 0,
-                parser: null,
-                parserOptions: {
-                    ecmaFeatures: {
-                        jsx: true,
-                        globalReturn: true
-                    }
-                },
-                plugins: {},
-                processor: null,
-                reportUnusedDisableDirectives: void 0,
-                rules: {},
-                settings: {}
-            });
-
-            // double-check that originals were not changed
-            assert.deepStrictEqual(config[0], { parserOptions: { ecmaFeatures: { jsx: true } } });
-            assert.deepStrictEqual(config[1], { parserOptions: { ecmaFeatures: { globalReturn: true } } });
-        });
-
-        it("should override configs when passed configs with the same ecmaFeatures", () => {
-            const config = [
-                { parserOptions: { ecmaFeatures: { globalReturn: false } } },
-                { parserOptions: { ecmaFeatures: { globalReturn: true } } }
-            ];
-
-            const result = merge(config[0], config[1]);
-
-            assert.deepStrictEqual(result, {
-                configNameOfNoInlineConfig: "",
-                env: {},
-                globals: {},
-                ignores: void 0,
-                noInlineConfig: void 0,
-                parser: null,
-                parserOptions: {
-                    ecmaFeatures: {
-                        globalReturn: true
-                    }
-                },
-                plugins: {},
-                processor: null,
-                reportUnusedDisableDirectives: void 0,
-                rules: {},
-                settings: {}
-            });
-        });
-
-        it("should combine configs and override rules when merging two configs with arrays and int", () => {
-
-            const config = [
-                { rules: { "no-mixed-requires": [0, false] } },
-                { rules: { "no-mixed-requires": 1 } }
-            ];
-
-            const result = merge(config[0], config[1]);
-
-            assert.isArray(result.rules["no-mixed-requires"]);
-            assert.strictEqual(result.rules["no-mixed-requires"][0], 1);
-            assert.strictEqual(result.rules["no-mixed-requires"][1], false);
-            assert.deepStrictEqual(config[0], { rules: { "no-mixed-requires": [0, false] } });
-            assert.deepStrictEqual(config[1], { rules: { "no-mixed-requires": 1 } });
-        });
-
-        it("should combine configs and override rules options completely", () => {
-
-            const config = [
-                { rules: { "no-mixed-requires1": [1, { event: ["evt", "e"] }] } },
-                { rules: { "no-mixed-requires1": [1, { err: ["error", "e"] }] } }
-            ];
-
-            const result = merge(config[0], config[1]);
-
-            assert.isArray(result.rules["no-mixed-requires1"]);
-            assert.deepStrictEqual(result.rules["no-mixed-requires1"][1], { err: ["error", "e"] });
-            assert.deepStrictEqual(config[0], { rules: { "no-mixed-requires1": [1, { event: ["evt", "e"] }] } });
-            assert.deepStrictEqual(config[1], { rules: { "no-mixed-requires1": [1, { err: ["error", "e"] }] } });
-        });
-
-        it("should combine configs and override rules options without array or object", () => {
-
-            const config = [
-                { rules: { "no-mixed-requires1": ["warn", "nconf", "underscore"] } },
-                { rules: { "no-mixed-requires1": [2, "requirejs"] } }
-            ];
-
-            const result = merge(config[0], config[1]);
-
-            assert.strictEqual(result.rules["no-mixed-requires1"][0], 2);
-            assert.strictEqual(result.rules["no-mixed-requires1"][1], "requirejs");
-            assert.isUndefined(result.rules["no-mixed-requires1"][2]);
-            assert.deepStrictEqual(config[0], { rules: { "no-mixed-requires1": ["warn", "nconf", "underscore"] } });
-            assert.deepStrictEqual(config[1], { rules: { "no-mixed-requires1": [2, "requirejs"] } });
-        });
-
-        it("should combine configs and override rules options without array or object but special case", () => {
-
-            const config = [
-                { rules: { "no-mixed-requires1": [1, "nconf", "underscore"] } },
-                { rules: { "no-mixed-requires1": "error" } }
-            ];
-
-            const result = merge(config[0], config[1]);
-
-            assert.strictEqual(result.rules["no-mixed-requires1"][0], "error");
-            assert.strictEqual(result.rules["no-mixed-requires1"][1], "nconf");
-            assert.strictEqual(result.rules["no-mixed-requires1"][2], "underscore");
-            assert.deepStrictEqual(config[0], { rules: { "no-mixed-requires1": [1, "nconf", "underscore"] } });
-            assert.deepStrictEqual(config[1], { rules: { "no-mixed-requires1": "error" } });
-        });
-
-        it("should combine configs correctly", () => {
-
-            const config = [
-                {
-                    rules: {
-                        "no-mixed-requires1": [1, { event: ["evt", "e"] }],
-                        "valid-jsdoc": 1,
-                        semi: 1,
-                        quotes1: [2, { exception: ["hi"] }],
-                        smile: [1, ["hi", "bye"]]
-                    },
-                    parserOptions: {
-                        ecmaFeatures: { jsx: true }
-                    },
-                    env: { browser: true },
-                    globals: { foo: false }
-                },
-                {
-                    rules: {
-                        "no-mixed-requires1": [1, { err: ["error", "e"] }],
-                        "valid-jsdoc": 2,
-                        test: 1,
-                        smile: [1, ["xxx", "yyy"]]
-                    },
-                    parserOptions: {
-                        ecmaFeatures: { globalReturn: true }
-                    },
-                    env: { browser: false },
-                    globals: { foo: true }
-                }
-            ];
-
-            const result = merge(config[0], config[1]);
-
-            assert.deepStrictEqual(result, {
-                configNameOfNoInlineConfig: "",
-                parser: null,
-                parserOptions: {
-                    ecmaFeatures: {
-                        jsx: true,
-                        globalReturn: true
-                    }
-                },
-                plugins: {},
-                env: {
-                    browser: false
-                },
-                globals: {
-                    foo: true
-                },
-                rules: {
-                    "no-mixed-requires1": [1,
-                        {
-                            err: [
-                                "error",
-                                "e"
-                            ]
-                        }
-                    ],
-                    quotes1: [2,
-                        {
-                            exception: [
-                                "hi"
-                            ]
-                        }
-                    ],
-                    semi: [1],
-                    smile: [1, ["xxx", "yyy"]],
-                    test: [1],
-                    "valid-jsdoc": [2]
-                },
-                settings: {},
-                processor: null,
-                noInlineConfig: void 0,
-                reportUnusedDisableDirectives: void 0,
-                ignores: void 0
-            });
-            assert.deepStrictEqual(config[0], {
-                rules: {
-                    "no-mixed-requires1": [1, { event: ["evt", "e"] }],
-                    "valid-jsdoc": 1,
-                    semi: 1,
-                    quotes1: [2, { exception: ["hi"] }],
-                    smile: [1, ["hi", "bye"]]
-                },
-                parserOptions: {
-                    ecmaFeatures: { jsx: true }
-                },
-                env: { browser: true },
-                globals: { foo: false }
-            });
-            assert.deepStrictEqual(config[1], {
-                rules: {
-                    "no-mixed-requires1": [1, { err: ["error", "e"] }],
-                    "valid-jsdoc": 2,
-                    test: 1,
-                    smile: [1, ["xxx", "yyy"]]
-                },
-                parserOptions: {
-                    ecmaFeatures: { globalReturn: true }
-                },
-                env: { browser: false },
-                globals: { foo: true }
-            });
-        });
-
-        it("should copy deeply if there is not the destination's property", () => {
-            const a = {};
-            const b = { settings: { bar: 1 } };
-
-            const result = merge(a, b);
-
-            assert(a.settings === void 0);
-            assert(b.settings.bar === 1);
-            assert(result.settings.bar === 1);
-
-            result.settings.bar = 2;
-            assert(b.settings.bar === 1);
-            assert(result.settings.bar === 2);
-        });
-    });
-
-    describe("'getUsedExtractedConfigs(instance)' function should retrieve used extracted configs from the instance's internal cache.", () => {
-        let configArray;
-
-        beforeEach(() => {
-            configArray = new ConfigArray(
-                {
-                    rules: {
-                        "no-redeclare": "error"
-                    }
-                },
-                {
-                    criteria: OverrideTester.create(["*.js"], [], process.cwd()),
-                    rules: {
-                        "no-undef": "error"
-                    }
-                },
-                {
-                    criteria: OverrideTester.create(["*.js"], [path.basename(__filename)], process.cwd()),
-                    rules: {
-                        "no-use-before-define": "error"
-                    }
-                },
-                {
-                    criteria: OverrideTester.create(["*.ts"], [], process.cwd()),
-                    rules: {
-                        "no-unused-vars": "error"
-                    }
-                }
-            );
-        });
-
-        it("should return empty array before it called 'extractConfig(filePath)'.", () => {
-            assert.deepStrictEqual(getUsedExtractedConfigs(configArray), []);
-        });
-
-        for (const { filePaths } of [
-            { filePaths: [__filename] },
-            { filePaths: [__filename, `${__filename}.ts`] },
-            { filePaths: [__filename, `${__filename}.ts`, path.join(__dirname, "foo.js")] }
-        ]) {
-            describe(`after it called 'extractConfig(filePath)' ${filePaths.length} time(s) with ${JSON.stringify(filePaths, null, 4)}, the returned array`, () => { // eslint-disable-line no-loop-func
-                let configs;
-                let usedConfigs;
-
-                beforeEach(() => {
-                    configs = filePaths.map(filePath => configArray.extractConfig(filePath));
-                    usedConfigs = getUsedExtractedConfigs(configArray);
-                });
-
-                it(`should have ${filePaths.length} as the length.`, () => {
-                    assert.strictEqual(usedConfigs.length, configs.length);
-                });
-
-                for (let i = 0; i < filePaths.length; ++i) {
-                    it(`should contain 'configs[${i}]'.`, () => { // eslint-disable-line no-loop-func
-                        assert(usedConfigs.includes(configs[i]));
-                    });
-                }
-            });
-        }
-
-        it("should not contain duplicate values.", () => {
-
-            // Call some times, including with the same arguments.
-            configArray.extractConfig(__filename);
-            configArray.extractConfig(`${__filename}.ts`);
-            configArray.extractConfig(path.join(__dirname, "foo.js"));
-            configArray.extractConfig(__filename);
-            configArray.extractConfig(path.join(__dirname, "foo.js"));
-            configArray.extractConfig(path.join(__dirname, "bar.js"));
-            configArray.extractConfig(path.join(__dirname, "baz.js"));
-
-            const usedConfigs = getUsedExtractedConfigs(configArray);
-
-            assert.strictEqual(new Set(usedConfigs).size, usedConfigs.length);
-        });
-    });
-});
diff --git a/eslint/tests/lib/cli-engine/config-array/config-dependency.js b/eslint/tests/lib/cli-engine/config-array/config-dependency.js
deleted file mode 100644 (file)
index fc008fa..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-/**
- * @fileoverview Tests for ConfigDependency class.
- * @author Toru Nagashima <https://github.com/mysticatea>
- */
-"use strict";
-
-const assert = require("assert");
-const { Console } = require("console");
-const { Writable } = require("stream");
-const { ConfigDependency } = require("../../../../lib/cli-engine/config-array/config-dependency");
-
-describe("ConfigDependency", () => {
-    describe("'constructor(data)' should initialize properties.", () => {
-
-        /** @type {ConfigDependency} */
-        let dep;
-
-        beforeEach(() => {
-            dep = new ConfigDependency({
-                definition: { name: "definition?" },
-                error: new Error("error?"),
-                filePath: "filePath?",
-                id: "id?",
-                importerName: "importerName?",
-                importerPath: "importerPath?"
-            });
-        });
-
-        it("should set 'data.definition' to 'definition' property.", () => {
-            assert.deepStrictEqual(dep.definition, { name: "definition?" });
-        });
-
-        it("should set 'data.error' to 'error' property.", () => {
-            assert.deepStrictEqual(dep.error.message, "error?");
-        });
-
-        it("should set 'data.filePath' to 'filePath' property.", () => {
-            assert.deepStrictEqual(dep.filePath, "filePath?");
-        });
-
-        it("should set 'data.id' to 'id' property.", () => {
-            assert.deepStrictEqual(dep.id, "id?");
-        });
-
-        it("should set 'data.importerName' to 'importerName' property.", () => {
-            assert.deepStrictEqual(dep.importerName, "importerName?");
-        });
-
-        it("should set 'data.importerPath' to 'importerPath' property.", () => {
-            assert.deepStrictEqual(dep.importerPath, "importerPath?");
-        });
-    });
-
-    describe("'JSON.stringify(...)' should return readable JSON; not include 'definition' property", () => {
-        it("should not print 'definition' property.", () => {
-            const dep = new ConfigDependency({
-                definition: { name: "definition?" },
-                error: new Error("error?"),
-                filePath: "filePath?",
-                id: "id?",
-                importerName: "importerName?",
-                importerPath: "importerPath?"
-            });
-
-            assert.deepStrictEqual(
-                JSON.parse(JSON.stringify(dep)),
-                {
-                    error: { message: "error?" },
-                    filePath: "filePath?",
-                    id: "id?",
-                    importerName: "importerName?",
-                    importerPath: "importerPath?"
-                }
-            );
-        });
-    });
-
-    describe("'console.log(...)' should print readable string; not include 'defininition' property", () => {
-
-        // Record the written strings to `output` variable.
-        let output = "";
-        const localConsole = new Console(
-            new class extends Writable {
-                write(chunk) { // eslint-disable-line class-methods-use-this
-                    output += chunk;
-                }
-            }()
-        );
-
-        it("should not print 'definition' property.", () => {
-            const error = new Error("error?"); // reuse error object to use the same stacktrace.
-            const dep = new ConfigDependency({
-                definition: { name: "definition?" },
-                error,
-                filePath: "filePath?",
-                id: "id?",
-                importerName: "importerName?",
-                importerPath: "importerPath?"
-            });
-
-            // Make actual output.
-            output = "";
-            localConsole.log(dep);
-            const actual = output;
-
-            // Make expected output; no `definition` property.
-            output = "";
-            localConsole.log({
-                error,
-                filePath: "filePath?",
-                id: "id?",
-                importerName: "importerName?",
-                importerPath: "importerPath?"
-            });
-            const expected = output;
-
-            assert.strictEqual(actual, expected);
-        });
-    });
-});
diff --git a/eslint/tests/lib/cli-engine/config-array/extracted-config.js b/eslint/tests/lib/cli-engine/config-array/extracted-config.js
deleted file mode 100644 (file)
index 9d70047..0000000
+++ /dev/null
@@ -1,139 +0,0 @@
-/**
- * @fileoverview Tests for ExtractedConfig class.
- * @author Toru Nagashima <https://github.com/mysticatea>
- */
-"use strict";
-
-const assert = require("assert");
-const { ExtractedConfig } = require("../../../../lib/cli-engine/config-array/extracted-config");
-
-describe("'ExtractedConfig' class", () => {
-    describe("'constructor()' should create an instance.", () => {
-
-        /** @type {ExtractedConfig} */
-        let config;
-
-        beforeEach(() => {
-            config = new ExtractedConfig();
-        });
-
-        it("should have 'env' property.", () => {
-            assert.deepStrictEqual(config.env, {});
-        });
-
-        it("should have 'globals' property.", () => {
-            assert.deepStrictEqual(config.globals, {});
-        });
-
-        it("should have 'parser' property.", () => {
-            assert.deepStrictEqual(config.parser, null);
-        });
-
-        it("should have 'parserOptions' property.", () => {
-            assert.deepStrictEqual(config.parserOptions, {});
-        });
-
-        it("should have 'plugins' property.", () => {
-            assert.deepStrictEqual(config.plugins, {});
-        });
-
-        it("should have 'processor' property.", () => {
-            assert.deepStrictEqual(config.processor, null);
-        });
-
-        it("should have 'rules' property.", () => {
-            assert.deepStrictEqual(config.rules, {});
-        });
-
-        it("should have 'settings' property.", () => {
-            assert.deepStrictEqual(config.settings, {});
-        });
-    });
-
-    describe("'toCompatibleObjectAsConfigFileContent()' method should return a valid config data.", () => {
-
-        /** @type {ExtractedConfig} */
-        let config;
-
-        beforeEach(() => {
-            config = new ExtractedConfig();
-        });
-
-        it("should use 'env' property as is.", () => {
-            config.env = { a: true };
-
-            const data = config.toCompatibleObjectAsConfigFileContent();
-
-            assert.deepStrictEqual(data.env, { a: true });
-        });
-
-        it("should use 'globals' as is.", () => {
-            config.globals = { a: true };
-
-            const data = config.toCompatibleObjectAsConfigFileContent();
-
-            assert.deepStrictEqual(data.globals, { a: true });
-        });
-
-        it("should use 'parser.filePath' for 'parser' property.", () => {
-            config.parser = {
-                definition: {},
-                error: null,
-                filePath: "/path/to/a/parser",
-                id: "parser",
-                importerName: "importer name",
-                importerPath: "importer path"
-            };
-
-            const data = config.toCompatibleObjectAsConfigFileContent();
-
-            assert.deepStrictEqual(data.parser, "/path/to/a/parser");
-        });
-
-        it("should use 'null' for 'parser' property if 'parser' property is 'null'.", () => {
-            const data = config.toCompatibleObjectAsConfigFileContent();
-
-            assert.deepStrictEqual(data.parser, null);
-        });
-
-        it("should use 'parserOptions' property as is.", () => {
-            config.parserOptions = { a: true };
-
-            const data = config.toCompatibleObjectAsConfigFileContent();
-
-            assert.deepStrictEqual(data.parserOptions, { a: true });
-        });
-
-        it("should use the keys of 'plugins' property for 'plugins' property.", () => {
-            config.plugins = { a: {}, b: {} };
-
-            const data = config.toCompatibleObjectAsConfigFileContent();
-
-            assert.deepStrictEqual(data.plugins, ["b", "a"]);
-        });
-
-        it("should not use 'processor' property.", () => {
-            config.processor = "foo/.md";
-
-            const data = config.toCompatibleObjectAsConfigFileContent();
-
-            assert.deepStrictEqual(data.processor, void 0);
-        });
-
-        it("should use 'rules' property as is.", () => {
-            config.rules = { a: 1, b: 2 };
-
-            const data = config.toCompatibleObjectAsConfigFileContent();
-
-            assert.deepStrictEqual(data.rules, { a: 1, b: 2 });
-        });
-
-        it("should use 'settings' property as is.", () => {
-            config.settings = { a: 1 };
-
-            const data = config.toCompatibleObjectAsConfigFileContent();
-
-            assert.deepStrictEqual(data.settings, { a: 1 });
-        });
-    });
-});
diff --git a/eslint/tests/lib/cli-engine/config-array/ignore-pattern.js b/eslint/tests/lib/cli-engine/config-array/ignore-pattern.js
deleted file mode 100644 (file)
index 9d652f3..0000000
+++ /dev/null
@@ -1,179 +0,0 @@
-/**
- * @fileoverview Tests for IgnorePattern class.
- * @author Toru Nagashima <https://github.com/mysticatea>
- */
-"use strict";
-
-const assert = require("assert");
-const path = require("path");
-const sinon = require("sinon");
-const { IgnorePattern } = require("../../../../lib/cli-engine/config-array/ignore-pattern");
-
-describe("IgnorePattern", () => {
-    describe("constructor(patterns, basePath)", () => {
-        it("should bind the first argument to 'patterns' property.", () => {
-            const p = new IgnorePattern(["a.js"], process.cwd());
-
-            assert.deepStrictEqual(p.patterns, ["a.js"]);
-        });
-
-        it("should bind the second argument to 'basePath' property.", () => {
-            const p = new IgnorePattern(["a.js"], process.cwd());
-
-            assert.strictEqual(p.basePath, process.cwd());
-        });
-
-        it("should throw an error if the second argument was not an absolute path.", () => {
-            assert.throws(() => new IgnorePattern([], "a.js"), "");
-        });
-    });
-
-    describe("getPatternsRelativeTo(newBasePath)", () => {
-        it("should return 'patterns' as-is if the argument is the same as 'basePath'.", () => {
-            const basePath1 = path.join(process.cwd(), "foo/bar");
-            const p = new IgnorePattern(["a.js", "/b.js", "!c.js", "!/d.js"], basePath1);
-
-            assert.deepStrictEqual(
-                p.getPatternsRelativeTo(basePath1),
-                ["a.js", "/b.js", "!c.js", "!/d.js"]
-            );
-        });
-
-        it("should return modified 'patterns' if the argument is different from 'basePath'.", () => {
-            const basePath1 = path.join(process.cwd(), "foo/bar");
-            const basePath2 = process.cwd();
-            const p = new IgnorePattern(["a.js", "/b.js", "!c.js", "!/d.js"], basePath1);
-
-            assert.deepStrictEqual(
-                p.getPatternsRelativeTo(basePath2),
-                ["/foo/bar/**/a.js", "/foo/bar/b.js", "!/foo/bar/**/c.js", "!/foo/bar/d.js"]
-            );
-        });
-    });
-
-    describe("static createIgnore(ignorePatterns)", () => {
-        describe("with two patterns should return a function, and the function", () => {
-
-            /**
-             * performs static createIgnre assertions against the cwd.
-             * @param {string} cwd cwd to be the base path for assertions
-             * @returns {void}
-             */
-            function assertions(cwd) {
-                const basePath1 = path.join(cwd, "foo/bar");
-                const basePath2 = path.join(cwd, "abc/");
-                const ignores = IgnorePattern.createIgnore([
-                    new IgnorePattern(["*.js", "/*.ts", "!a.*", "!/b.*"], basePath1),
-                    new IgnorePattern(["*.js", "/*.ts", "!a.*", "!/b.*"], basePath2)
-                ]);
-                const patterns = [
-                    ["a.js", false],
-                    ["a.ts", false],
-                    ["b.js", false],
-                    ["b.ts", false],
-                    ["c.js", false],
-                    ["c.ts", false],
-                    ["dir/a.js", false],
-                    ["dir/a.ts", false],
-                    ["dir/b.js", false],
-                    ["dir/b.ts", false],
-                    ["dir/c.js", false],
-                    ["dir/c.ts", false],
-                    ["foo/bar/a.js", false],
-                    ["foo/bar/a.ts", false],
-                    ["foo/bar/b.js", false],
-                    ["foo/bar/b.ts", false],
-                    ["foo/bar/c.js", true],
-                    ["foo/bar/c.ts", true],
-                    ["foo/bar/dir/a.js", false],
-                    ["foo/bar/dir/a.ts", false],
-                    ["foo/bar/dir/b.js", true],
-                    ["foo/bar/dir/b.ts", false],
-                    ["foo/bar/dir/c.js", true],
-                    ["foo/bar/dir/c.ts", false],
-                    ["abc/a.js", false],
-                    ["abc/a.ts", false],
-                    ["abc/b.js", false],
-                    ["abc/b.ts", false],
-                    ["abc/c.js", true],
-                    ["abc/c.ts", true],
-                    ["abc/dir/a.js", false],
-                    ["abc/dir/a.ts", false],
-                    ["abc/dir/b.js", true],
-                    ["abc/dir/b.ts", false],
-                    ["abc/dir/c.js", true],
-                    ["abc/dir/c.ts", false]
-                ];
-
-                for (const [filename, expected] of patterns) {
-                    it(`should return ${expected} if '${filename}' was given.`, () => {
-                        assert.strictEqual(ignores(path.join(cwd, filename)), expected);
-                    });
-                }
-
-                it("should return false if '.dot.js' and false was given.", () => {
-                    assert.strictEqual(ignores(path.join(cwd, ".dot.js"), false), true);
-                });
-
-                it("should return true if '.dot.js' and true were given.", () => {
-                    assert.strictEqual(ignores(path.join(cwd, ".dot.js"), true), false);
-                });
-
-                it("should return false if '.dot/foo.js' and false was given.", () => {
-                    assert.strictEqual(ignores(path.join(cwd, ".dot/foo.js"), false), true);
-                });
-
-                it("should return true if '.dot/foo.js' and true were given.", () => {
-                    assert.strictEqual(ignores(path.join(cwd, ".dot/foo.js"), true), false);
-                });
-            }
-
-            assertions(process.cwd());
-
-            /*
-             * This will catch regressions of Windows specific issue #12850 when run on CI nodes.
-             * This runs the full set of assertions for the function returned from IgnorePattern.createIgnore.
-             * When run on Windows CI nodes the .root drive i.e C:\ will be supplied
-             *  forcing getCommonAncestors to resolve to the root of the drive thus catching any regrssion of 12850.
-             * When run on *nix CI nodes provides additional coverage on this OS too.
-             *  assertions when run on Windows CI nodes and / on *nix OS
-             */
-            assertions(path.parse(process.cwd()).root);
-        });
-    });
-
-    describe("static createIgnore(ignorePatterns)", () => {
-
-        /*
-         * This test will catch regressions of Windows specific issue #12850 when run on your local dev box
-         * irrespective of if you are running a Windows or *nix style OS.
-         * When running on *nix sinon is used to emulate Windows behaviors of path and platform APIs
-         * thus ensuring that the Windows specific fix is exercised and any regression is caught.
-         */
-        it("with common ancestor of drive root on windows should not throw", () => {
-            try {
-
-                /*
-                 * When not on Windows return win32 values so local runs on *nix hit the same code path as on Windows
-                 * thus enabling developers with *nix style OS to catch and debug any future regression of #12850 without
-                 * requiring a Windows based OS.
-                 */
-                if (process.platform !== "win32") {
-                    sinon.stub(process, "platform").value("win32");
-                    sinon.stub(path, "sep").value(path.win32.sep);
-                    sinon.replace(path, "isAbsolute", path.win32.isAbsolute);
-                }
-
-                const ignores = IgnorePattern.createIgnore([
-                    new IgnorePattern(["*.js"], "C:\\foo\\bar"),
-                    new IgnorePattern(["*.js"], "C:\\abc\\")
-                ]);
-
-                // calls to this should not throw when getCommonAncestor returns root of drive
-                ignores("C:\\abc\\contract.d.ts");
-            } finally {
-                sinon.restore();
-            }
-        });
-    });
-});
diff --git a/eslint/tests/lib/cli-engine/config-array/override-tester.js b/eslint/tests/lib/cli-engine/config-array/override-tester.js
deleted file mode 100644 (file)
index 4920c44..0000000
+++ /dev/null
@@ -1,286 +0,0 @@
-/**
- * @fileoverview Tests for OverrideTester class.
- * @author Toru Nagashima <https://github.com/mysticatea>
- */
-"use strict";
-
-const assert = require("assert");
-const { Console } = require("console");
-const path = require("path");
-const { Writable } = require("stream");
-const { OverrideTester } = require("../../../../lib/cli-engine/config-array/override-tester");
-
-describe("OverrideTester", () => {
-    describe("'create(files, excludedFiles, basePath)' should create a tester.", () => {
-        for (const { files, excludedFiles, basePath } of [
-            { files: void 0, excludedFiles: void 0, basePath: process.cwd() },
-            { files: [], excludedFiles: [], basePath: process.cwd() }
-        ]) {
-            it(`should return null if ${JSON.stringify({ files, excludedFiles, basePath })} was given.`, () => {
-                assert.strictEqual(
-                    OverrideTester.create(files, excludedFiles, basePath),
-                    null
-                );
-            });
-        }
-
-        it("should return an 'OverrideTester' instance that has given parameters if strings were given.", () => {
-            const files = "*.js";
-            const excludedFiles = "ignore/*";
-            const basePath = process.cwd();
-            const tester = OverrideTester.create(files, excludedFiles, basePath);
-
-            assert.strictEqual(tester.patterns.length, 1);
-            assert.strictEqual(tester.patterns[0].includes.length, 1);
-            assert.strictEqual(tester.patterns[0].excludes.length, 1);
-            assert.strictEqual(tester.patterns[0].includes[0].pattern, files);
-            assert.strictEqual(tester.patterns[0].excludes[0].pattern, excludedFiles);
-            assert.strictEqual(tester.basePath, basePath);
-        });
-
-        it("should return an 'OverrideTester' instance that has given parameters if arrays were given.", () => {
-            const files = ["*.js"];
-            const excludedFiles = ["ignore/*"];
-            const basePath = process.cwd();
-            const tester = OverrideTester.create(files, excludedFiles, basePath);
-
-            assert.strictEqual(tester.patterns.length, 1);
-            assert.strictEqual(tester.patterns[0].includes.length, 1);
-            assert.strictEqual(tester.patterns[0].excludes.length, 1);
-            assert.strictEqual(tester.patterns[0].includes[0].pattern, files[0]);
-            assert.strictEqual(tester.patterns[0].excludes[0].pattern, excludedFiles[0]);
-            assert.strictEqual(tester.basePath, basePath);
-        });
-    });
-
-    describe("'and(a, b)' should return either or create another tester what includes both.", () => {
-        it("should return null if both were null.", () => {
-            assert.strictEqual(OverrideTester.and(null, null), null);
-        });
-
-        it("should return a new tester with the the first one's properties if the second one was null.", () => {
-            const tester = OverrideTester.create("*.js", null, process.cwd());
-            const result = OverrideTester.and(tester, null);
-
-            assert.notStrictEqual(result, tester);
-            assert.strictEqual(result.patterns, tester.patterns);
-            assert.strictEqual(result.basePath, tester.basePath);
-        });
-
-        it("should return a new tester with the the second one's properties if the first one was null.", () => {
-            const tester = OverrideTester.create("*.js", null, process.cwd());
-            const result = OverrideTester.and(null, tester);
-
-            assert.notStrictEqual(result, tester);
-            assert.strictEqual(result.patterns, tester.patterns);
-            assert.strictEqual(result.basePath, tester.basePath);
-        });
-
-        it("should return another one what includes both patterns if both are testers.", () => {
-            const tester1 = OverrideTester.create("*.js");
-            const tester2 = OverrideTester.create("*.ts");
-            const tester3 = OverrideTester.and(tester1, tester2);
-
-            assert.strictEqual(tester3.patterns.length, 2);
-            assert.strictEqual(tester3.patterns[0], tester1.patterns[0]);
-            assert.strictEqual(tester3.patterns[1], tester2.patterns[0]);
-        });
-    });
-
-    describe("'test(filePath)' method", () => {
-        it("should throw an error if no arguments were given.", () => {
-            assert.throws(() => {
-                OverrideTester.create(["*.js"], [], process.cwd()).test();
-            }, /'filePath' should be an absolute path, but got undefined/u);
-        });
-
-        it("should throw an error if a non-string value was given.", () => {
-            assert.throws(() => {
-                OverrideTester.create(["*.js"], [], process.cwd()).test(100);
-            }, /'filePath' should be an absolute path, but got 100/u);
-        });
-
-        it("should throw an error if a relative path was given.", () => {
-            assert.throws(() => {
-                OverrideTester.create(["*.js"], [], process.cwd()).test("foo/bar.js");
-            }, /'filePath' should be an absolute path, but got foo\/bar\.js/u);
-        });
-
-        it("should return true only when both conditions are matched if the tester was created by 'and' factory function.", () => {
-            const tester = OverrideTester.and(
-                OverrideTester.create(["*.js"], [], process.cwd()),
-                OverrideTester.create(["test/**"], [], process.cwd())
-            );
-
-            assert.strictEqual(tester.test(path.resolve("test/a.js")), true);
-            assert.strictEqual(tester.test(path.resolve("lib/a.js")), false);
-            assert.strictEqual(tester.test(path.resolve("test/a.ts")), false);
-        });
-
-        /**
-         * Test if a given file path matches to the given condition.
-         *
-         * The test cases which depend on this function were moved from
-         * 'tests/lib/config/config-ops.js' when refactoring to keep the
-         * cumulated test cases.
-         *
-         * Previously, the testing logic of `overrides` properties had been
-         * implemented in `ConfigOps.pathMatchesGlobs()` function. But
-         * currently, it's implemented in `OverrideTester` class.
-         * @param {string} filePath The file path to test patterns against
-         * @param {string|string[]} files One or more glob patterns
-         * @param {string|string[]} [excludedFiles] One or more glob patterns
-         * @returns {boolean} The result.
-         */
-        function test(filePath, files, excludedFiles) {
-            const basePath = process.cwd();
-            const tester = OverrideTester.create(files, excludedFiles, basePath);
-
-            return tester.test(path.resolve(basePath, filePath));
-        }
-
-        /**
-         * Emits a test that confirms the specified file path matches the specified combination of patterns.
-         * @param {string} filePath The file path to test patterns against
-         * @param {string|string[]} patterns One or more glob patterns
-         * @param {string|string[]} [excludedPatterns] One or more glob patterns
-         * @returns {void}
-         */
-        function match(filePath, patterns, excludedPatterns) {
-            it(`matches ${filePath} given '${patterns.join("','")}' includes and '${excludedPatterns.join("','")}' excludes`, () => {
-                const result = test(filePath, patterns, excludedPatterns);
-
-                assert.strictEqual(result, true);
-            });
-        }
-
-        /**
-         * Emits a test that confirms the specified file path does not match the specified combination of patterns.
-         * @param {string} filePath The file path to test patterns against
-         * @param {string|string[]} patterns One or more glob patterns
-         * @param {string|string[]} [excludedPatterns] One or more glob patterns
-         * @returns {void}
-         */
-        function noMatch(filePath, patterns, excludedPatterns) {
-            it(`does not match ${filePath} given '${patterns.join("','")}' includes and '${excludedPatterns.join("','")}' excludes`, () => {
-                const result = test(filePath, patterns, excludedPatterns);
-
-                assert.strictEqual(result, false);
-            });
-        }
-
-        /**
-         * Emits a test that confirms the specified pattern throws an error.
-         * @param {string} filePath The file path to test the pattern against
-         * @param {string} pattern The glob pattern that should trigger the error condition
-         * @param {string} expectedMessage The expected error's message
-         * @returns {void}
-         */
-        function error(filePath, pattern, expectedMessage) {
-            it(`emits an error given '${pattern}'`, () => {
-                let errorMessage;
-
-                try {
-                    test(filePath, pattern);
-                } catch (e) {
-                    errorMessage = e.message;
-                }
-
-                assert.strictEqual(errorMessage, expectedMessage);
-            });
-        }
-
-        // files in the project root
-        match("foo.js", ["foo.js"], []);
-        match("foo.js", ["*"], []);
-        match("foo.js", ["*.js"], []);
-        match("foo.js", ["**/*.js"], []);
-        match("bar.js", ["*.js"], ["foo.js"]);
-        match("foo.js", ["./foo.js"], []);
-        match("foo.js", ["./*"], []);
-        match("foo.js", ["./**"], []);
-
-        noMatch("foo.js", ["*"], ["foo.js"]);
-        noMatch("foo.js", ["*.js"], ["foo.js"]);
-        noMatch("foo.js", ["**/*.js"], ["foo.js"]);
-
-        // files in a subdirectory
-        match("subdir/foo.js", ["foo.js"], []);
-        match("subdir/foo.js", ["*"], []);
-        match("subdir/foo.js", ["*.js"], []);
-        match("subdir/foo.js", ["**/*.js"], []);
-        match("subdir/foo.js", ["subdir/*.js"], []);
-        match("subdir/foo.js", ["subdir/foo.js"], []);
-        match("subdir/foo.js", ["subdir/*"], []);
-        match("subdir/second/foo.js", ["subdir/**"], []);
-        match("subdir/foo.js", ["./**"], []);
-        match("subdir/foo.js", ["./subdir/**"], []);
-        match("subdir/foo.js", ["./subdir/*"], []);
-
-        noMatch("subdir/foo.js", ["./foo.js"], []);
-        noMatch("subdir/foo.js", ["*"], ["subdir/**"]);
-        noMatch("subdir/very/deep/foo.js", ["*.js"], ["subdir/**"]);
-        noMatch("subdir/second/foo.js", ["subdir/*"], []);
-        noMatch("subdir/second/foo.js", ["subdir/**"], ["subdir/second/*"]);
-
-        // error conditions
-        error("foo.js", ["/*.js"], "Invalid override pattern (expected relative path not containing '..'): /*.js");
-        error("foo.js", ["/foo.js"], "Invalid override pattern (expected relative path not containing '..'): /foo.js");
-        error("foo.js", ["../**"], "Invalid override pattern (expected relative path not containing '..'): ../**");
-    });
-
-    describe("'JSON.stringify(...)' should return readable JSON; not include 'Minimatch' objects", () => {
-        it("should return an object that has three properties 'includes', 'excludes', and 'basePath' if that 'patterns' property include one object.", () => {
-            const files = "*.js";
-            const excludedFiles = "test/*";
-            const basePath = process.cwd();
-            const tester = OverrideTester.create(files, excludedFiles, basePath);
-
-            assert.strictEqual(
-                JSON.stringify(tester),
-                `{"includes":["${files}"],"excludes":["${excludedFiles}"],"basePath":${JSON.stringify(basePath)}}`
-            );
-        });
-
-        it("should return an object that has two properties 'AND' and 'basePath' if that 'patterns' property include two or more objects.", () => {
-            const files1 = "*.js";
-            const excludedFiles1 = "test/*";
-            const files2 = "*.story.js";
-            const excludedFiles2 = "src/*";
-            const basePath = process.cwd();
-            const tester = OverrideTester.and(
-                OverrideTester.create(files1, excludedFiles1, basePath),
-                OverrideTester.create(files2, excludedFiles2, basePath)
-            );
-
-            assert.deepStrictEqual(
-                JSON.parse(JSON.stringify(tester)),
-                {
-                    AND: [
-                        { includes: [files1], excludes: [excludedFiles1] },
-                        { includes: [files2], excludes: [excludedFiles2] }
-                    ],
-                    basePath
-                }
-            );
-        });
-    });
-
-    describe("'console.log(...)' should print readable string; not include 'Minimatch' objects", () => {
-        const localConsole = new Console(new Writable());
-
-        it("should use 'toJSON()' method.", () => {
-            const tester = OverrideTester.create("*.js", "", process.cwd());
-            let called = false;
-
-            tester.toJSON = () => {
-                called = true;
-                return "";
-            };
-
-            localConsole.log(tester);
-
-            assert(called);
-        });
-    });
-});
index 43728862d95ce3bedc2c26e303e2b1b81cd83e96..3ea51a408c8872b4a7404237facff8643a44149e 100644 (file)
@@ -10,30 +10,24 @@ const os = require("os");
 const { assert } = require("chai");
 const sh = require("shelljs");
 const { CascadingConfigArrayFactory } =
-    require("../../../lib/cli-engine/cascading-config-array-factory");
-const { defineFileEnumeratorWithInMemoryFileSystem } = require("../../_utils");
+    require("@eslint/eslintrc/lib/cascading-config-array-factory");
+const { createCustomTeardown } = require("../../_utils");
+const { FileEnumerator } = require("../../../lib/cli-engine/file-enumerator");
 
 describe("FileEnumerator", () => {
     describe("'iterateFiles(patterns)' method should iterate files and configs.", () => {
         describe("with three directories ('lib', 'lib/nested', 'test') that contains 'one.js' and 'two.js'", () => {
             const root = path.join(os.tmpdir(), "eslint/file-enumerator");
             const files = {
-                /* eslint-disable quote-props */
-                "lib": {
-                    "nested": {
-                        "one.js": "",
-                        "two.js": "",
-                        "parser.js": "",
-                        ".eslintrc.yml": "parser: './parser'"
-                    },
-                    "one.js": "",
-                    "two.js": ""
-                },
-                "test": {
-                    "one.js": "",
-                    "two.js": "",
-                    ".eslintrc.yml": "env: { mocha: true }"
-                },
+                "lib/nested/one.js": "",
+                "lib/nested/two.js": "",
+                "lib/nested/parser.js": "",
+                "lib/nested/.eslintrc.yml": "parser: './parser'",
+                "lib/one.js": "",
+                "lib/two.js": "",
+                "test/one.js": "",
+                "test/two.js": "",
+                "test/.eslintrc.yml": "env: { mocha: true }",
                 ".eslintignore": "/lib/nested/parser.js",
                 ".eslintrc.json": JSON.stringify({
                     rules: {
@@ -41,17 +35,19 @@ describe("FileEnumerator", () => {
                         "no-unused-vars": "error"
                     }
                 })
-                /* eslint-enable quote-props */
             };
-            const { FileEnumerator } = defineFileEnumeratorWithInMemoryFileSystem({ cwd: () => root, files });
+            const { prepare, cleanup, getPath } = createCustomTeardown({ cwd: root, files });
 
             /** @type {FileEnumerator} */
             let enumerator;
 
-            beforeEach(() => {
-                enumerator = new FileEnumerator();
+            beforeEach(async () => {
+                await prepare();
+                enumerator = new FileEnumerator({ cwd: getPath() });
             });
 
+            afterEach(cleanup);
+
             it("should ignore empty strings.", () => {
                 Array.from(enumerator.iterateFiles(["lib/*.js", ""])); // don't throw "file not found" error.
             });
@@ -177,7 +173,6 @@ describe("FileEnumerator", () => {
 
         // This group moved from 'tests/lib/util/glob-utils.js' when refactoring to keep the cumulated test cases.
         describe("with 'tests/fixtures/glob-utils' files", () => {
-            const { FileEnumerator } = require("../../../lib/cli-engine/file-enumerator");
             let fixtureDir;
 
             /**
@@ -211,7 +206,16 @@ describe("FileEnumerator", () => {
                 );
             }
 
-            before(() => {
+            before(function() {
+
+                /*
+                 * GitHub Actions Windows and macOS runners occasionally
+                 * exhibit extremely slow filesystem operations, during which
+                 * copying fixtures exceeds the default test timeout, so raise
+                 * it just for this hook. Mocha uses `this` to set timeouts on
+                 * an individual hook level.
+                 */
+                this.timeout(60 * 1000); // eslint-disable-line no-invalid-this
                 fixtureDir = `${os.tmpdir()}/eslint/tests/fixtures/`;
                 sh.mkdir("-p", fixtureDir);
                 sh.cp("-r", "./tests/fixtures/*", fixtureDir);
@@ -484,4 +488,31 @@ describe("FileEnumerator", () => {
             });
         });
     });
+
+    // https://github.com/eslint/eslint/issues/13789
+    describe("constructor default values when config extends eslint:recommended", () => {
+        const root = path.join(os.tmpdir(), "eslint/file-enumerator");
+        const files = {
+            "file.js": "",
+            ".eslintrc.json": JSON.stringify({
+                extends: ["eslint:recommended"]
+            })
+        };
+        const { prepare, cleanup, getPath } = createCustomTeardown({ cwd: root, files });
+
+
+        /** @type {FileEnumerator} */
+        let enumerator;
+
+        beforeEach(async () => {
+            await prepare();
+            enumerator = new FileEnumerator({ cwd: getPath() });
+        });
+
+        afterEach(cleanup);
+
+        it("should not throw an exception iterating files", () => {
+            Array.from(enumerator.iterateFiles(["."]));
+        });
+    });
 });
index 2a72da15ece5fec7603d7795c82639e48d485cf9..218b15310abc53c574afc581debb6e14b3e5aec9 100644 (file)
@@ -151,4 +151,21 @@ describe("formatter:checkstyle", () => {
             assert.strictEqual(result, "<?xml version=\"1.0\" encoding=\"utf-8\"?><checkstyle version=\"4.3\"><file name=\"foo.js\"><error line=\"5\" column=\"10\" severity=\"error\" message=\"Unexpected foo.\" source=\"\" /></file></checkstyle>");
         });
     });
+
+    describe("when passing single message without line and column", () => {
+        const code = [{
+            filePath: "foo.js",
+            messages: [{
+                message: "Unexpected foo.",
+                severity: 2,
+                ruleId: "foo"
+            }]
+        }];
+
+        it("should return line and column as 0 instead of undefined", () => {
+            const result = formatter(code);
+
+            assert.strictEqual(result, "<?xml version=\"1.0\" encoding=\"utf-8\"?><checkstyle version=\"4.3\"><file name=\"foo.js\"><error line=\"0\" column=\"0\" severity=\"error\" message=\"Unexpected foo. (foo)\" source=\"eslint.rules.foo\" /></file></checkstyle>");
+        });
+    });
 });
index a1d9a23e49176bb9ef6a98158ed599ca4995f539..0f8a24c2e28975cb22da24b82bc25ed6779877a7 100644 (file)
@@ -82,7 +82,15 @@ describe("cli", () => {
     }
 
     // copy into clean area so as not to get "infected" by this project's .eslintrc files
-    before(() => {
+    before(function() {
+
+        /*
+         * GitHub Actions Windows and macOS runners occasionally exhibit
+         * extremely slow filesystem operations, during which copying fixtures
+         * exceeds the default test timeout, so raise it just for this hook.
+         * Mocha uses `this` to set timeouts on an individual hook level.
+         */
+        this.timeout(60 * 1000); // eslint-disable-line no-invalid-this
         fixtureDir = `${os.tmpdir()}/eslint/fixtures`;
         sh.mkdir("-p", fixtureDir);
         sh.cp("-r", "./tests/fixtures/.", fixtureDir);
index 6807a57aa8ad0de05c75da5d3fb3b14bd3b9b3d6..e4f37f404c4f7fd3c361518ead375f4646dac5a6 100644 (file)
@@ -16,13 +16,12 @@ const os = require("os");
 const path = require("path");
 const escapeStringRegExp = require("escape-string-regexp");
 const fCache = require("file-entry-cache");
-const leche = require("leche");
 const sinon = require("sinon");
 const proxyquire = require("proxyquire").noCallThru().noPreserveCache();
 const shell = require("shelljs");
-const { CascadingConfigArrayFactory } = require("../../../lib/cli-engine/cascading-config-array-factory");
+const { CascadingConfigArrayFactory } = require("@eslint/eslintrc/lib/cascading-config-array-factory");
 const hash = require("../../../lib/cli-engine/hash");
-const { unIndent, defineESLintWithInMemoryFileSystem } = require("../../_utils");
+const { unIndent, createCustomTeardown } = require("../../_utils");
 
 //------------------------------------------------------------------------------
 // Tests
@@ -87,7 +86,15 @@ describe("ESLint", () => {
     }
 
     // copy into clean area so as not to get "infected" by this project's .eslintrc files
-    before(() => {
+    before(function() {
+
+        /*
+         * GitHub Actions Windows and macOS runners occasionally exhibit
+         * extremely slow filesystem operations, during which copying fixtures
+         * exceeds the default test timeout, so raise it just for this hook.
+         * Mocha uses `this` to set timeouts on an individual hook level.
+         */
+        this.timeout(60 * 1000); // eslint-disable-line no-invalid-this
         shell.mkdir("-p", fixtureDir);
         shell.cp("-r", "./tests/fixtures/.", fixtureDir);
     });
@@ -900,7 +907,7 @@ describe("ESLint", () => {
                 overrideConfig: {
                     parser: "espree",
                     parserOptions: {
-                        ecmaVersion: 2020
+                        ecmaVersion: 2021
                     }
                 },
                 useEslintrc: false
@@ -2938,26 +2945,31 @@ describe("ESLint", () => {
         });
 
         describe("a config file setting should have higher priority than a shareable config file's settings always; https://github.com/eslint/eslint/issues/11510", () => {
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: path.join(os.tmpdir(), "eslint/11510"),
+                files: {
+                    "no-console-error-in-overrides.json": JSON.stringify({
+                        overrides: [{
+                            files: ["*.js"],
+                            rules: { "no-console": "error" }
+                        }]
+                    }),
+                    ".eslintrc.json": JSON.stringify({
+                        extends: "./no-console-error-in-overrides.json",
+                        rules: { "no-console": "off" }
+                    }),
+                    "a.js": "console.log();"
+                }
+            });
+
             beforeEach(() => {
-                ({ ESLint } = defineESLintWithInMemoryFileSystem({
-                    cwd: () => path.join(os.tmpdir(), "eslint/11510"),
-                    files: {
-                        "no-console-error-in-overrides.json": JSON.stringify({
-                            overrides: [{
-                                files: ["*.js"],
-                                rules: { "no-console": "error" }
-                            }]
-                        }),
-                        ".eslintrc.json": JSON.stringify({
-                            extends: "./no-console-error-in-overrides.json",
-                            rules: { "no-console": "off" }
-                        }),
-                        "a.js": "console.log();"
-                    }
-                }));
-                eslint = new ESLint();
+                eslint = new ESLint({ cwd: getPath() });
+                return prepare();
             });
 
+            afterEach(cleanup);
+
             it("should not report 'no-console' error.", async () => {
                 const results = await eslint.lintFiles("a.js");
 
@@ -2967,11 +2979,11 @@ describe("ESLint", () => {
         });
 
         describe("configs of plugin rules should be validated even if 'plugins' key doesn't exist; https://github.com/eslint/eslint/issues/11559", () => {
-            beforeEach(() => {
-                ({ ESLint } = defineESLintWithInMemoryFileSystem({
-                    cwd: () => path.join(os.tmpdir(), "eslint/11559"),
-                    files: {
-                        "node_modules/eslint-plugin-test/index.js": `
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: path.join(os.tmpdir(), "eslint/11559"),
+                files: {
+                    "node_modules/eslint-plugin-test/index.js": `
                             exports.configs = {
                                 recommended: { plugins: ["test"] }
                             };
@@ -2982,20 +2994,26 @@ describe("ESLint", () => {
                                 }
                             };
                         `,
-                        ".eslintrc.json": JSON.stringify({
+                    ".eslintrc.json": JSON.stringify({
 
-                            // Import via the recommended config.
-                            extends: "plugin:test/recommended",
+                        // Import via the recommended config.
+                        extends: "plugin:test/recommended",
 
-                            // Has invalid option.
-                            rules: { "test/foo": ["error", "invalid-option"] }
-                        }),
-                        "a.js": "console.log();"
-                    }
-                }));
-                eslint = new ESLint();
+                        // Has invalid option.
+                        rules: { "test/foo": ["error", "invalid-option"] }
+                    }),
+                    "a.js": "console.log();"
+                }
             });
 
+            beforeEach(() => {
+                eslint = new ESLint({ cwd: getPath() });
+                return prepare();
+            });
+
+            afterEach(cleanup);
+
+
             it("should throw fatal error.", async () => {
                 await assert.rejects(async () => {
                     await eslint.lintFiles("a.js");
@@ -3004,11 +3022,10 @@ describe("ESLint", () => {
         });
 
         describe("'--fix-type' should not crash even if plugin rules exist; https://github.com/eslint/eslint/issues/11586", () => {
-            beforeEach(() => {
-                ({ ESLint } = defineESLintWithInMemoryFileSystem({
-                    cwd: () => path.join(os.tmpdir(), "eslint/11586"),
-                    files: {
-                        "node_modules/eslint-plugin-test/index.js": `
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: path.join(os.tmpdir(), "cli-engine/11586"),
+                files: {
+                    "node_modules/eslint-plugin-test/index.js": `
                             exports.rules = {
                                 "no-example": {
                                     meta: { type: "problem", fixable: "code" },
@@ -3028,16 +3045,26 @@ describe("ESLint", () => {
                                 }
                             };
                         `,
-                        ".eslintrc.json": JSON.stringify({
-                            plugins: ["test"],
-                            rules: { "test/no-example": "error" }
-                        }),
-                        "a.js": "example;"
-                    }
-                }));
-                eslint = new ESLint({ fix: true, fixTypes: ["problem"] });
+                    ".eslintrc.json": {
+                        plugins: ["test"],
+                        rules: { "test/no-example": "error" }
+                    },
+                    "a.js": "example;"
+                }
             });
 
+            beforeEach(() => {
+                eslint = new ESLint({
+                    cwd: getPath(),
+                    fix: true,
+                    fixTypes: ["problem"]
+                });
+
+                return prepare();
+            });
+
+            afterEach(cleanup);
+
             it("should not crash.", async () => {
                 const results = await eslint.lintFiles("a.js");
 
@@ -3088,18 +3115,29 @@ describe("ESLint", () => {
                 `
             };
 
+            let cleanup;
+
+            beforeEach(() => {
+                cleanup = () => { };
+            });
+
+            afterEach(() => cleanup());
+
             it("should lint only JavaScript blocks if '--ext' was not given.", async () => {
-                ESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         ...commonFiles,
-                        ".eslintrc.json": JSON.stringify({
+                        ".eslintrc.json": {
                             plugins: ["markdown", "html"],
                             rules: { semi: "error" }
-                        })
+                        }
                     }
-                }).ESLint;
-                eslint = new ESLint({ cwd: root });
+                });
+
+                cleanup = teardown.cleanup;
+                await teardown.prepare();
+                eslint = new ESLint({ cwd: teardown.getPath() });
                 const results = await eslint.lintFiles(["test.md"]);
 
                 assert.strictEqual(results.length, 1);
@@ -3109,17 +3147,20 @@ describe("ESLint", () => {
             });
 
             it("should fix only JavaScript blocks if '--ext' was not given.", async () => {
-                ESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         ...commonFiles,
-                        ".eslintrc.json": JSON.stringify({
+                        ".eslintrc.json": {
                             plugins: ["markdown", "html"],
                             rules: { semi: "error" }
-                        })
+                        }
                     }
-                }).ESLint;
-                eslint = new ESLint({ cwd: root, fix: true });
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                eslint = new ESLint({ cwd: teardown.getPath(), fix: true });
                 const results = await eslint.lintFiles(["test.md"]);
 
                 assert.strictEqual(results.length, 1);
@@ -3141,17 +3182,20 @@ describe("ESLint", () => {
             });
 
             it("should lint HTML blocks as well with multiple processors if '--ext' option was given.", async () => {
-                ESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         ...commonFiles,
-                        ".eslintrc.json": JSON.stringify({
+                        ".eslintrc.json": {
                             plugins: ["markdown", "html"],
                             rules: { semi: "error" }
-                        })
+                        }
                     }
-                }).ESLint;
-                eslint = new ESLint({ cwd: root, extensions: ["js", "html"] });
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                eslint = new ESLint({ cwd: teardown.getPath(), extensions: ["js", "html"] });
                 const results = await eslint.lintFiles(["test.md"]);
 
                 assert.strictEqual(results.length, 1);
@@ -3163,17 +3207,20 @@ describe("ESLint", () => {
             });
 
             it("should fix HTML blocks as well with multiple processors if '--ext' option was given.", async () => {
-                ESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         ...commonFiles,
-                        ".eslintrc.json": JSON.stringify({
+                        ".eslintrc.json": {
                             plugins: ["markdown", "html"],
                             rules: { semi: "error" }
-                        })
+                        }
                     }
-                }).ESLint;
-                eslint = new ESLint({ cwd: root, extensions: ["js", "html"], fix: true });
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                eslint = new ESLint({ cwd: teardown.getPath(), extensions: ["js", "html"], fix: true });
                 const results = await eslint.lintFiles(["test.md"]);
 
                 assert.strictEqual(results.length, 1);
@@ -3194,12 +3241,12 @@ describe("ESLint", () => {
                 `);
             });
 
-            it("should use overriden processor; should report HTML blocks but not fix HTML blocks if the processor for '*.html' didn't support autofix.", async () => {
-                ESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
+            it("should use overridden processor; should report HTML blocks but not fix HTML blocks if the processor for '*.html' didn't support autofix.", async () => {
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         ...commonFiles,
-                        ".eslintrc.json": JSON.stringify({
+                        ".eslintrc.json": {
                             plugins: ["markdown", "html"],
                             rules: { semi: "error" },
                             overrides: [
@@ -3208,10 +3255,13 @@ describe("ESLint", () => {
                                     processor: "html/non-fixable" // supportsAutofix: false
                                 }
                             ]
-                        })
+                        }
                     }
-                }).ESLint;
-                eslint = new ESLint({ cwd: root, extensions: ["js", "html"], fix: true });
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                eslint = new ESLint({ cwd: teardown.getPath(), extensions: ["js", "html"], fix: true });
                 const results = await eslint.lintFiles(["test.md"]);
 
                 assert.strictEqual(results.length, 1);
@@ -3236,11 +3286,11 @@ describe("ESLint", () => {
             });
 
             it("should use the config '**/*.html/*.js' to lint JavaScript blocks in HTML.", async () => {
-                ESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         ...commonFiles,
-                        ".eslintrc.json": JSON.stringify({
+                        ".eslintrc.json": {
                             plugins: ["markdown", "html"],
                             rules: { semi: "error" },
                             overrides: [
@@ -3261,10 +3311,13 @@ describe("ESLint", () => {
                                     }
                                 }
                             ]
-                        })
+                        }
                     }
-                }).ESLint;
-                eslint = new ESLint({ cwd: root, extensions: ["js", "html"] });
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                eslint = new ESLint({ cwd: teardown.getPath(), extensions: ["js", "html"] });
                 const results = await eslint.lintFiles(["test.md"]);
 
                 assert.strictEqual(results.length, 1);
@@ -3276,11 +3329,11 @@ describe("ESLint", () => {
             });
 
             it("should use the same config as one which has 'processor' property in order to lint blocks in HTML if the processor was legacy style.", async () => {
-                ESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         ...commonFiles,
-                        ".eslintrc.json": JSON.stringify({
+                        ".eslintrc.json": {
                             plugins: ["markdown", "html"],
                             rules: { semi: "error" },
                             overrides: [
@@ -3300,10 +3353,13 @@ describe("ESLint", () => {
                                     }
                                 }
                             ]
-                        })
+                        }
                     }
-                }).ESLint;
-                eslint = new ESLint({ cwd: root, extensions: ["js", "html"] });
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                eslint = new ESLint({ cwd: teardown.getPath(), extensions: ["js", "html"] });
                 const results = await eslint.lintFiles(["test.md"]);
 
                 assert.strictEqual(results.length, 1);
@@ -3317,17 +3373,20 @@ describe("ESLint", () => {
             });
 
             it("should throw an error if invalid processor was specified.", async () => {
-                ESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         ...commonFiles,
-                        ".eslintrc.json": JSON.stringify({
+                        ".eslintrc.json": {
                             plugins: ["markdown", "html"],
                             processor: "markdown/unknown"
-                        })
+                        }
                     }
-                }).ESLint;
-                eslint = new ESLint({ cwd: root });
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                eslint = new ESLint({ cwd: teardown.getPath() });
 
                 await assert.rejects(async () => {
                     await eslint.lintFiles(["test.md"]);
@@ -3335,11 +3394,11 @@ describe("ESLint", () => {
             });
 
             it("should lint HTML blocks as well with multiple processors if 'overrides[].files' is present.", async () => {
-                ESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         ...commonFiles,
-                        ".eslintrc.json": JSON.stringify({
+                        ".eslintrc.json": {
                             plugins: ["markdown", "html"],
                             rules: { semi: "error" },
                             overrides: [
@@ -3352,10 +3411,13 @@ describe("ESLint", () => {
                                     processor: "markdown/.md"
                                 }
                             ]
-                        })
+                        }
                     }
-                }).ESLint;
-                eslint = new ESLint({ cwd: root });
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                eslint = new ESLint({ cwd: teardown.getPath() });
                 const results = await eslint.lintFiles(["test.md"]);
 
                 assert.strictEqual(results.length, 1);
@@ -3466,27 +3528,33 @@ describe("ESLint", () => {
         });
 
         describe("with '--rulesdir' option", () => {
-            it("should use the configured rules which are defined by '--rulesdir' option.", async () => {
-                const rootPath = getFixturePath("cli-engine/with-rulesdir");
-                const StubbedESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => rootPath,
-                    files: {
-                        "internal-rules/test.js": `
+
+            const rootPath = getFixturePath("cli-engine/with-rulesdir");
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: rootPath,
+                files: {
+                    "internal-rules/test.js": `
                             module.exports = context => ({
                                 ExpressionStatement(node) {
                                     context.report({ node, message: "ok" })
                                 }
                             })
                         `,
-                        ".eslintrc.json": JSON.stringify({
-                            root: true,
-                            rules: { test: "error" }
-                        }),
-                        "test.js": "console.log('hello')"
-                    }
-                }).ESLint;
+                    ".eslintrc.json": {
+                        root: true,
+                        rules: { test: "error" }
+                    },
+                    "test.js": "console.log('hello')"
+                }
+            });
 
-                eslint = new StubbedESLint({
+            beforeEach(prepare);
+            afterEach(cleanup);
+
+
+            it("should use the configured rules which are defined by '--rulesdir' option.", async () => {
+                eslint = new ESLint({
+                    cwd: getPath(),
                     rulePaths: ["internal-rules"]
                 });
                 const results = await eslint.lintFiles(["test.js"]);
@@ -3500,9 +3568,18 @@ describe("ESLint", () => {
         describe("glob pattern '[ab].js'", () => {
             const root = getFixturePath("cli-engine/unmatched-glob");
 
+            let cleanup;
+
+            beforeEach(() => {
+                cleanup = () => { };
+            });
+
+            afterEach(() => cleanup());
+
             it("should match '[ab].js' if existed.", async () => {
-                ESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
+
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         "a.js": "",
                         "b.js": "",
@@ -3510,8 +3587,12 @@ describe("ESLint", () => {
                         "[ab].js": "",
                         ".eslintrc.yml": "root: true"
                     }
-                }).ESLint;
-                eslint = new ESLint();
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+
+                eslint = new ESLint({ cwd: teardown.getPath() });
                 const results = await eslint.lintFiles(["[ab].js"]);
                 const filenames = results.map(r => path.basename(r.filePath));
 
@@ -3519,16 +3600,19 @@ describe("ESLint", () => {
             });
 
             it("should match 'a.js' and 'b.js' if '[ab].js' didn't existed.", async () => {
-                ESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         "a.js": "",
                         "b.js": "",
                         "ab.js": "",
                         ".eslintrc.yml": "root: true"
                     }
-                }).ESLint;
-                eslint = new ESLint();
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                eslint = new ESLint({ cwd: teardown.getPath() });
                 const results = await eslint.lintFiles(["[ab].js"]);
                 const filenames = results.map(r => path.basename(r.filePath));
 
@@ -3539,15 +3623,27 @@ describe("ESLint", () => {
         describe("with 'noInlineConfig' setting", () => {
             const root = getFixturePath("cli-engine/noInlineConfig");
 
+            let cleanup;
+
+            beforeEach(() => {
+                cleanup = () => { };
+            });
+
+            afterEach(() => cleanup());
+
             it("should warn directive comments if 'noInlineConfig' was given.", async () => {
-                ESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         "test.js": "/* globals foo */",
                         ".eslintrc.yml": "noInlineConfig: true"
                     }
-                }).ESLint;
-                eslint = new ESLint();
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                eslint = new ESLint({ cwd: teardown.getPath() });
+
                 const results = await eslint.lintFiles(["test.js"]);
                 const messages = results[0].messages;
 
@@ -3556,15 +3652,19 @@ describe("ESLint", () => {
             });
 
             it("should show the config file what the 'noInlineConfig' came from.", async () => {
-                ESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         "node_modules/eslint-config-foo/index.js": "module.exports = {noInlineConfig: true}",
                         "test.js": "/* globals foo */",
                         ".eslintrc.yml": "extends: foo"
                     }
-                }).ESLint;
-                eslint = new ESLint();
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                eslint = new ESLint({ cwd: teardown.getPath() });
+
                 const results = await eslint.lintFiles(["test.js"]);
                 const messages = results[0].messages;
 
@@ -3576,15 +3676,28 @@ describe("ESLint", () => {
         describe("with 'reportUnusedDisableDirectives' setting", () => {
             const root = getFixturePath("cli-engine/reportUnusedDisableDirectives");
 
+            let cleanup;
+
+            beforeEach(() => {
+                cleanup = () => { };
+            });
+
+            afterEach(() => cleanup());
+
             it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives' was given.", async () => {
-                ESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         "test.js": "/* eslint-disable eqeqeq */",
                         ".eslintrc.yml": "reportUnusedDisableDirectives: true"
                     }
-                }).ESLint;
-                eslint = new ESLint();
+                });
+
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                eslint = new ESLint({ cwd: teardown.getPath() });
+
                 const results = await eslint.lintFiles(["test.js"]);
                 const messages = results[0].messages;
 
@@ -3595,14 +3708,22 @@ describe("ESLint", () => {
 
             describe("the runtime option overrides config files.", () => {
                 it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives=off' was given in runtime.", async () => {
-                    ESLint = defineESLintWithInMemoryFileSystem({
-                        cwd: () => root,
+                    const teardown = createCustomTeardown({
+                        cwd: root,
                         files: {
                             "test.js": "/* eslint-disable eqeqeq */",
                             ".eslintrc.yml": "reportUnusedDisableDirectives: true"
                         }
-                    }).ESLint;
-                    eslint = new ESLint({ reportUnusedDisableDirectives: "off" });
+                    });
+
+                    await teardown.prepare();
+                    cleanup = teardown.cleanup;
+
+                    eslint = new ESLint({
+                        cwd: teardown.getPath(),
+                        reportUnusedDisableDirectives: "off"
+                    });
+
                     const results = await eslint.lintFiles(["test.js"]);
                     const messages = results[0].messages;
 
@@ -3610,14 +3731,22 @@ describe("ESLint", () => {
                 });
 
                 it("should warn unused 'eslint-disable' comments as error if 'reportUnusedDisableDirectives=error' was given in runtime.", async () => {
-                    ESLint = defineESLintWithInMemoryFileSystem({
-                        cwd: () => root,
+                    const teardown = createCustomTeardown({
+                        cwd: root,
                         files: {
                             "test.js": "/* eslint-disable eqeqeq */",
                             ".eslintrc.yml": "reportUnusedDisableDirectives: true"
                         }
-                    }).ESLint;
-                    eslint = new ESLint({ reportUnusedDisableDirectives: "error" });
+                    });
+
+                    await teardown.prepare();
+                    cleanup = teardown.cleanup;
+
+                    eslint = new ESLint({
+                        cwd: teardown.getPath(),
+                        reportUnusedDisableDirectives: "error"
+                    });
+
                     const results = await eslint.lintFiles(["test.js"]);
                     const messages = results[0].messages;
 
@@ -3630,25 +3759,28 @@ describe("ESLint", () => {
 
         describe("with 'overrides[*].extends' setting on deep locations", () => {
             const root = getFixturePath("cli-engine/deeply-overrides-i-extends");
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
+                        overrides: [{ files: ["*test*"], extends: "two" }]
+                    })}`,
+                    "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({
+                        overrides: [{ files: ["*.js"], extends: "three" }]
+                    })}`,
+                    "node_modules/eslint-config-three/index.js": `module.exports = ${JSON.stringify({
+                        rules: { "no-console": "error" }
+                    })}`,
+                    "test.js": "console.log('hello')",
+                    ".eslintrc.yml": "extends: one"
+                }
+            });
+
+            beforeEach(prepare);
+            afterEach(cleanup);
 
             it("should not throw.", async () => {
-                ESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
-                            overrides: [{ files: ["*test*"], extends: "two" }]
-                        })}`,
-                        "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({
-                            overrides: [{ files: ["*.js"], extends: "three" }]
-                        })}`,
-                        "node_modules/eslint-config-three/index.js": `module.exports = ${JSON.stringify({
-                            rules: { "no-console": "error" }
-                        })}`,
-                        "test.js": "console.log('hello')",
-                        ".eslintrc.yml": "extends: one"
-                    }
-                }).ESLint;
-                eslint = new ESLint();
+                eslint = new ESLint({ cwd: getPath() });
                 const results = await eslint.lintFiles(["test.js"]);
                 const messages = results[0].messages;
 
@@ -3660,46 +3792,71 @@ describe("ESLint", () => {
         describe("don't ignore the entry directory.", () => {
             const root = getFixturePath("cli-engine/dont-ignore-entry-dir");
 
+            let cleanup;
+
+            beforeEach(() => {
+                cleanup = () => { };
+            });
+
+            afterEach(async () => {
+                await cleanup();
+
+                const configFilePath = path.resolve(root, "../.eslintrc.json");
+
+                if (shell.test("-e", configFilePath)) {
+                    shell.rm(configFilePath);
+                }
+            });
+
             it("'lintFiles(\".\")' should not load config files from outside of \".\".", async () => {
-                ESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
                         "../.eslintrc.json": "BROKEN FILE",
                         ".eslintrc.json": JSON.stringify({ root: true }),
                         "index.js": "console.log(\"hello\")"
                     }
-                }).ESLint;
-                eslint = new ESLint();
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                eslint = new ESLint({ cwd: teardown.getPath() });
 
                 // Don't throw "failed to load config file" error.
                 await eslint.lintFiles(".");
             });
 
             it("'lintFiles(\".\")' should not ignore '.' even if 'ignorePatterns' contains it.", async () => {
-                ESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
-                        "../.eslintrc.json": JSON.stringify({ ignorePatterns: ["/dont-ignore-entry-dir"] }),
-                        ".eslintrc.json": JSON.stringify({ root: true }),
+                        "../.eslintrc.json": { ignorePatterns: ["/dont-ignore-entry-dir"] },
+                        ".eslintrc.json": { root: true },
                         "index.js": "console.log(\"hello\")"
                     }
-                }).ESLint;
-                eslint = new ESLint();
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                eslint = new ESLint({ cwd: teardown.getPath() });
 
                 // Don't throw "file not found" error.
                 await eslint.lintFiles(".");
             });
 
             it("'lintFiles(\"subdir\")' should not ignore './subdir' even if 'ignorePatterns' contains it.", async () => {
-                ESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
+                const teardown = createCustomTeardown({
+                    cwd: root,
                     files: {
-                        ".eslintrc.json": JSON.stringify({ ignorePatterns: ["/subdir"] }),
-                        "subdir/.eslintrc.json": JSON.stringify({ root: true }),
+                        ".eslintrc.json": { ignorePatterns: ["/subdir"] },
+                        "subdir/.eslintrc.json": { root: true },
                         "subdir/index.js": "console.log(\"hello\")"
                     }
-                }).ESLint;
-                eslint = new ESLint();
+                });
+
+                await teardown.prepare();
+                cleanup = teardown.cleanup;
+                eslint = new ESLint({ cwd: teardown.getPath() });
 
                 // Don't throw "file not found" error.
                 await eslint.lintFiles("subdir");
@@ -3762,6 +3919,26 @@ describe("ESLint", () => {
 
             await assert.rejects(() => eslint.calculateConfigForFile(null), /'filePath' must be a non-empty string/u);
         });
+
+        // https://github.com/eslint/eslint/issues/13793
+        it("should throw with an invalid built-in rule config", async () => {
+            const options = {
+                baseConfig: {
+                    rules: {
+                        "no-alert": ["error", {
+                            thisDoesNotExist: true
+                        }]
+                    }
+                }
+            };
+            const engine = new ESLint(options);
+            const filePath = getFixturePath("single-quoted.js");
+
+            await assert.rejects(
+                () => engine.calculateConfigForFile(filePath),
+                /Configuration for rule "no-alert" is invalid:/u
+            );
+        });
     });
 
     describe("isPathIgnored", () => {
@@ -4427,11 +4604,14 @@ describe("ESLint", () => {
         });
 
         it("should call fs.writeFile() for each result with output", async () => {
-            const fakeFS = leche.fake(fs);
-            const spy = fakeFS.writeFile = sinon.spy(callLastArgument);
-            const localESLint = proxyquire("../../../lib/eslint/eslint", {
+            const fakeFS = {
+                writeFile: sinon.spy(callLastArgument)
+            };
+            const spy = fakeFS.writeFile;
+            const { ESLint: localESLint } = proxyquire("../../../lib/eslint/eslint", {
                 fs: fakeFS
-            }).ESLint;
+            });
+
             const results = [
                 {
                     filePath: path.resolve("foo.js"),
@@ -4451,11 +4631,13 @@ describe("ESLint", () => {
         });
 
         it("should call fs.writeFile() for each result with output and not at all for a result without output", async () => {
-            const fakeFS = leche.fake(fs);
-            const spy = fakeFS.writeFile = sinon.spy(callLastArgument);
-            const localESLint = proxyquire("../../../lib/eslint/eslint", {
+            const fakeFS = {
+                writeFile: sinon.spy(callLastArgument)
+            };
+            const spy = fakeFS.writeFile;
+            const { ESLint: localESLint } = proxyquire("../../../lib/eslint/eslint", {
                 fs: fakeFS
-            }).ESLint;
+            });
             const results = [
                 {
                     filePath: path.resolve("foo.js"),
@@ -4627,41 +4809,39 @@ describe("ESLint", () => {
     describe("with ignorePatterns config", () => {
         const root = getFixturePath("cli-engine/ignore-patterns");
 
-        /** @type {typeof ESLint} */
-        let InMemoryESLint;
-
         describe("ignorePatterns can add an ignore pattern ('foo.js').", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.json": JSON.stringify({
-                            ignorePatterns: "foo.js"
-                        }),
-                        "foo.js": "",
-                        "bar.js": "",
-                        "subdir/foo.js": "",
-                        "subdir/bar.js": ""
-                    }
-                }).ESLint;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.json": {
+                        ignorePatterns: "foo.js"
+                    },
+                    "foo.js": "",
+                    "bar.js": "",
+                    "subdir/foo.js": "",
+                    "subdir/bar.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("foo.js"), true);
                 assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true);
             });
 
             it("'isPathIgnored()' should return 'false' for 'bar.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("bar.js"), false);
                 assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), false);
             });
 
             it("'lintFiles()' should not verify 'foo.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
                 const filePaths = (await engine.lintFiles("**/*.js"))
                     .map(r => r.filePath)
                     .sort();
@@ -4674,39 +4854,40 @@ describe("ESLint", () => {
         });
 
         describe("ignorePatterns can add ignore patterns ('foo.js', '/bar.js').", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.json": JSON.stringify({
-                            ignorePatterns: ["foo.js", "/bar.js"]
-                        }),
-                        "foo.js": "",
-                        "bar.js": "",
-                        "baz.js": "",
-                        "subdir/foo.js": "",
-                        "subdir/bar.js": "",
-                        "subdir/baz.js": ""
-                    }
-                }).ESLint;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.json": {
+                        ignorePatterns: ["foo.js", "/bar.js"]
+                    },
+                    "foo.js": "",
+                    "bar.js": "",
+                    "baz.js": "",
+                    "subdir/foo.js": "",
+                    "subdir/bar.js": "",
+                    "subdir/baz.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("foo.js"), true);
                 assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true);
             });
 
             it("'isPathIgnored()' should return 'true' for '/bar.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("bar.js"), true);
                 assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), false);
             });
 
             it("'lintFiles()' should not verify 'foo.js' and '/bar.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
                 const filePaths = (await engine.lintFiles("**/*.js"))
                     .map(r => r.filePath)
                     .sort();
@@ -4720,41 +4901,43 @@ describe("ESLint", () => {
         });
 
         describe("ignorePatterns can unignore '/node_modules/foo'.", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.json": JSON.stringify({
-                            ignorePatterns: "!/node_modules/foo"
-                        }),
-                        "node_modules/foo/index.js": "",
-                        "node_modules/foo/.dot.js": "",
-                        "node_modules/bar/index.js": "",
-                        "foo.js": ""
-                    }
-                }).ESLint;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.json": {
+                        ignorePatterns: "!/node_modules/foo"
+                    },
+                    "node_modules/foo/index.js": "",
+                    "node_modules/foo/.dot.js": "",
+                    "node_modules/bar/index.js": "",
+                    "foo.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'false' for 'node_modules/foo/index.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("node_modules/foo/index.js"), false);
             });
 
             it("'isPathIgnored()' should return 'true' for 'node_modules/foo/.dot.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("node_modules/foo/.dot.js"), true);
             });
 
             it("'isPathIgnored()' should return 'true' for 'node_modules/bar/index.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("node_modules/bar/index.js"), true);
             });
 
             it("'lintFiles()' should verify 'node_modules/foo/index.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
                 const filePaths = (await engine.lintFiles("**/*.js"))
                     .map(r => r.filePath)
                     .sort();
@@ -4767,26 +4950,28 @@ describe("ESLint", () => {
         });
 
         describe("ignorePatterns can unignore '.eslintrc.js'.", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.js": `module.exports = ${JSON.stringify({
-                            ignorePatterns: "!.eslintrc.js"
-                        })}`,
-                        "foo.js": ""
-                    }
-                }).ESLint;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.js": `module.exports = ${JSON.stringify({
+                        ignorePatterns: "!.eslintrc.js"
+                    })}`,
+                    "foo.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'false' for '.eslintrc.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored(".eslintrc.js"), false);
             });
 
             it("'lintFiles()' should verify '.eslintrc.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
                 const filePaths = (await engine.lintFiles("**/*.js"))
                     .map(r => r.filePath)
                     .sort();
@@ -4799,34 +4984,35 @@ describe("ESLint", () => {
         });
 
         describe(".eslintignore can re-ignore files that are unignored by ignorePatterns.", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.js": `module.exports = ${JSON.stringify({
-                            ignorePatterns: "!.*"
-                        })}`,
-                        ".eslintignore": ".foo*",
-                        ".foo.js": "",
-                        ".bar.js": ""
-                    }
-                }).ESLint;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.js": `module.exports = ${JSON.stringify({
+                        ignorePatterns: "!.*"
+                    })}`,
+                    ".eslintignore": ".foo*",
+                    ".foo.js": "",
+                    ".bar.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'true' for re-ignored '.foo.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored(".foo.js"), true);
             });
 
             it("'isPathIgnored()' should return 'false' for unignored '.bar.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored(".bar.js"), false);
             });
 
             it("'lintFiles()' should not verify re-ignored '.foo.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
                 const filePaths = (await engine.lintFiles("**/*.js"))
                     .map(r => r.filePath)
                     .sort();
@@ -4839,34 +5025,35 @@ describe("ESLint", () => {
         });
 
         describe(".eslintignore can unignore files that are ignored by ignorePatterns.", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.js": `module.exports = ${JSON.stringify({
-                            ignorePatterns: "*.js"
-                        })}`,
-                        ".eslintignore": "!foo.js",
-                        "foo.js": "",
-                        "bar.js": ""
-                    }
-                }).ESLint;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.js": `module.exports = ${JSON.stringify({
+                        ignorePatterns: "*.js"
+                    })}`,
+                    ".eslintignore": "!foo.js",
+                    "foo.js": "",
+                    "bar.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("foo.js"), false);
             });
 
             it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("bar.js"), true);
             });
 
             it("'lintFiles()' should verify unignored 'foo.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
                 const filePaths = (await engine.lintFiles("**/*.js"))
                     .map(r => r.filePath)
                     .sort();
@@ -4878,28 +5065,30 @@ describe("ESLint", () => {
         });
 
         describe("ignorePatterns in the config file in a child directory affects to only in the directory.", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.json": JSON.stringify({
-                            ignorePatterns: "foo.js"
-                        }),
-                        "subdir/.eslintrc.json": JSON.stringify({
-                            ignorePatterns: "bar.js"
-                        }),
-                        "foo.js": "",
-                        "bar.js": "",
-                        "subdir/foo.js": "",
-                        "subdir/bar.js": "",
-                        "subdir/subsubdir/foo.js": "",
-                        "subdir/subsubdir/bar.js": ""
-                    }
-                }).ESLint;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.json": JSON.stringify({
+                        ignorePatterns: "foo.js"
+                    }),
+                    "subdir/.eslintrc.json": JSON.stringify({
+                        ignorePatterns: "bar.js"
+                    }),
+                    "foo.js": "",
+                    "bar.js": "",
+                    "subdir/foo.js": "",
+                    "subdir/bar.js": "",
+                    "subdir/subsubdir/foo.js": "",
+                    "subdir/subsubdir/bar.js": ""
+                }
             });
 
+
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("foo.js"), true);
                 assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true);
@@ -4907,20 +5096,20 @@ describe("ESLint", () => {
             });
 
             it("'isPathIgnored()' should return 'true' for 'bar.js' in 'subdir'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), true);
                 assert.strictEqual(await engine.isPathIgnored("subdir/subsubdir/bar.js"), true);
             });
 
             it("'isPathIgnored()' should return 'false' for 'bar.js' in the outside of 'subdir'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("bar.js"), false);
             });
 
             it("'lintFiles()' should verify 'bar.js' in the outside of 'subdir'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
                 const filePaths = (await engine.lintFiles("**/*.js"))
                     .map(r => r.filePath)
                     .sort();
@@ -4932,36 +5121,37 @@ describe("ESLint", () => {
         });
 
         describe("ignorePatterns in the config file in a child directory can unignore the ignored files in the parent directory's config.", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.json": JSON.stringify({
-                            ignorePatterns: "foo.js"
-                        }),
-                        "subdir/.eslintrc.json": JSON.stringify({
-                            ignorePatterns: "!foo.js"
-                        }),
-                        "foo.js": "",
-                        "subdir/foo.js": ""
-                    }
-                }).ESLint;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.json": JSON.stringify({
+                        ignorePatterns: "foo.js"
+                    }),
+                    "subdir/.eslintrc.json": JSON.stringify({
+                        ignorePatterns: "!foo.js"
+                    }),
+                    "foo.js": "",
+                    "subdir/foo.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'true' for 'foo.js' in the root directory.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("foo.js"), true);
             });
 
             it("'isPathIgnored()' should return 'false' for 'foo.js' in the child directory.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), false);
             });
 
             it("'lintFiles()' should verify 'foo.js' in the child directory.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
                 const filePaths = (await engine.lintFiles("**/*.js"))
                     .map(r => r.filePath)
                     .sort();
@@ -4973,37 +5163,38 @@ describe("ESLint", () => {
         });
 
         describe(".eslintignore can unignore files that are ignored by ignorePatterns in the config file in the child directory.", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.json": JSON.stringify({}),
-                        "subdir/.eslintrc.json": JSON.stringify({
-                            ignorePatterns: "*.js"
-                        }),
-                        ".eslintignore": "!foo.js",
-                        "foo.js": "",
-                        "subdir/foo.js": "",
-                        "subdir/bar.js": ""
-                    }
-                }).ESLint;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.json": JSON.stringify({}),
+                    "subdir/.eslintrc.json": JSON.stringify({
+                        ignorePatterns: "*.js"
+                    }),
+                    ".eslintignore": "!foo.js",
+                    "foo.js": "",
+                    "subdir/foo.js": "",
+                    "subdir/bar.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("foo.js"), false);
                 assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), false);
             });
 
             it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), true);
             });
 
             it("'lintFiles()' should verify unignored 'foo.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
                 const filePaths = (await engine.lintFiles("**/*.js"))
                     .map(r => r.filePath)
                     .sort();
@@ -5016,51 +5207,52 @@ describe("ESLint", () => {
         });
 
         describe("if the config in a child directory has 'root:true', ignorePatterns in the config file in the parent directory should not be used.", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.json": JSON.stringify({
-                            ignorePatterns: "foo.js"
-                        }),
-                        "subdir/.eslintrc.json": JSON.stringify({
-                            root: true,
-                            ignorePatterns: "bar.js"
-                        }),
-                        "foo.js": "",
-                        "bar.js": "",
-                        "subdir/foo.js": "",
-                        "subdir/bar.js": ""
-                    }
-                }).ESLint;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.json": JSON.stringify({
+                        ignorePatterns: "foo.js"
+                    }),
+                    "subdir/.eslintrc.json": JSON.stringify({
+                        root: true,
+                        ignorePatterns: "bar.js"
+                    }),
+                    "foo.js": "",
+                    "bar.js": "",
+                    "subdir/foo.js": "",
+                    "subdir/bar.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'true' for 'foo.js' in the root directory.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("foo.js"), true);
             });
 
             it("'isPathIgnored()' should return 'false' for 'bar.js' in the root directory.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("bar.js"), false);
             });
 
             it("'isPathIgnored()' should return 'false' for 'foo.js' in the child directory.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), false);
             });
 
             it("'isPathIgnored()' should return 'true' for 'bar.js' in the child directory.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), true);
             });
 
             it("'lintFiles()' should verify 'bar.js' in the root directory and 'foo.js' in the child directory.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
                 const filePaths = (await engine.lintFiles("**/*.js"))
                     .map(r => r.filePath)
                     .sort();
@@ -5073,45 +5265,46 @@ describe("ESLint", () => {
         });
 
         describe("even if the config in a child directory has 'root:true', .eslintignore should be used.", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.json": JSON.stringify({}),
-                        "subdir/.eslintrc.json": JSON.stringify({
-                            root: true,
-                            ignorePatterns: "bar.js"
-                        }),
-                        ".eslintignore": "foo.js",
-                        "foo.js": "",
-                        "bar.js": "",
-                        "subdir/foo.js": "",
-                        "subdir/bar.js": ""
-                    }
-                }).ESLint;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.json": JSON.stringify({}),
+                    "subdir/.eslintrc.json": JSON.stringify({
+                        root: true,
+                        ignorePatterns: "bar.js"
+                    }),
+                    ".eslintignore": "foo.js",
+                    "foo.js": "",
+                    "bar.js": "",
+                    "subdir/foo.js": "",
+                    "subdir/bar.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("foo.js"), true);
                 assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true);
             });
 
             it("'isPathIgnored()' should return 'false' for 'bar.js' in the root directory.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("bar.js"), false);
             });
 
             it("'isPathIgnored()' should return 'true' for 'bar.js' in the child directory.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), true);
             });
 
             it("'lintFiles()' should verify 'bar.js' in the root directory.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
                 const filePaths = (await engine.lintFiles("**/*.js"))
                     .map(r => r.filePath)
                     .sort();
@@ -5123,36 +5316,37 @@ describe("ESLint", () => {
         });
 
         describe("ignorePatterns in the shareable config should be used.", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
-                            ignorePatterns: "foo.js"
-                        })}`,
-                        ".eslintrc.json": JSON.stringify({
-                            extends: "one"
-                        }),
-                        "foo.js": "",
-                        "bar.js": ""
-                    }
-                }).ESLint;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
+                        ignorePatterns: "foo.js"
+                    })}`,
+                    ".eslintrc.json": JSON.stringify({
+                        extends: "one"
+                    }),
+                    "foo.js": "",
+                    "bar.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("foo.js"), true);
             });
 
             it("'isPathIgnored()' should return 'false' for 'bar.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("bar.js"), false);
             });
 
             it("'lintFiles()' should verify 'bar.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
                 const filePaths = (await engine.lintFiles("**/*.js"))
                     .map(r => r.filePath)
                     .sort();
@@ -5164,36 +5358,38 @@ describe("ESLint", () => {
         });
 
         describe("ignorePatterns in the shareable config should be relative to the entry config file.", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
-                            ignorePatterns: "/foo.js"
-                        })}`,
-                        ".eslintrc.json": JSON.stringify({
-                            extends: "one"
-                        }),
-                        "foo.js": "",
-                        "subdir/foo.js": ""
-                    }
-                }).ESLint;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
+                        ignorePatterns: "/foo.js"
+                    })}`,
+                    ".eslintrc.json": JSON.stringify({
+                        extends: "one"
+                    }),
+                    "foo.js": "",
+                    "subdir/foo.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("foo.js"), true);
             });
 
             it("'isPathIgnored()' should return 'false' for 'subdir/foo.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), false);
             });
 
             it("'lintFiles()' should verify 'subdir/foo.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
                 const filePaths = (await engine.lintFiles("**/*.js"))
                     .map(r => r.filePath)
                     .sort();
@@ -5205,37 +5401,38 @@ describe("ESLint", () => {
         });
 
         describe("ignorePatterns in a config file can unignore the files which are ignored by ignorePatterns in the shareable config.", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
-                            ignorePatterns: "*.js"
-                        })}`,
-                        ".eslintrc.json": JSON.stringify({
-                            extends: "one",
-                            ignorePatterns: "!bar.js"
-                        }),
-                        "foo.js": "",
-                        "bar.js": ""
-                    }
-                }).ESLint;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
+                        ignorePatterns: "*.js"
+                    })}`,
+                    ".eslintrc.json": JSON.stringify({
+                        extends: "one",
+                        ignorePatterns: "!bar.js"
+                    }),
+                    "foo.js": "",
+                    "bar.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("foo.js"), true);
             });
 
             it("'isPathIgnored()' should return 'false' for 'bar.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
 
                 assert.strictEqual(await engine.isPathIgnored("bar.js"), false);
             });
 
             it("'lintFiles()' should verify 'bar.js'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
                 const filePaths = (await engine.lintFiles("**/*.js"))
                     .map(r => r.filePath)
                     .sort();
@@ -5247,26 +5444,28 @@ describe("ESLint", () => {
         });
 
         describe("ignorePatterns in a config file should not be used if --no-ignore option was given.", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.json": JSON.stringify({
-                            ignorePatterns: "*.js"
-                        }),
-                        "foo.js": ""
-                    }
-                }).ESLint;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.json": JSON.stringify({
+                        ignorePatterns: "*.js"
+                    }),
+                    "foo.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'isPathIgnored()' should return 'false' for 'foo.js'.", async () => {
-                const engine = new InMemoryESLint({ ignore: false });
+                const engine = new ESLint({ cwd: getPath(), ignore: false });
 
                 assert.strictEqual(await engine.isPathIgnored("foo.js"), false);
             });
 
             it("'lintFiles()' should verify 'foo.js'.", async () => {
-                const engine = new InMemoryESLint({ ignore: false });
+                const engine = new ESLint({ cwd: getPath(), ignore: false });
                 const filePaths = (await engine.lintFiles("**/*.js"))
                     .map(r => r.filePath)
                     .sort();
@@ -5278,26 +5477,28 @@ describe("ESLint", () => {
         });
 
         describe("ignorePatterns in overrides section is not allowed.", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.js": `module.exports = ${JSON.stringify({
-                            overrides: [
-                                {
-                                    files: "*.js",
-                                    ignorePatterns: "foo.js"
-                                }
-                            ]
-                        })}`,
-                        "foo.js": ""
-                    }
-                }).ESLint;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.js": `module.exports = ${JSON.stringify({
+                        overrides: [
+                            {
+                                files: "*.js",
+                                ignorePatterns: "foo.js"
+                            }
+                        ]
+                    })}`,
+                    "foo.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("should throw a configuration error.", async () => {
                 await assert.rejects(async () => {
-                    const engine = new InMemoryESLint();
+                    const engine = new ESLint({ cwd: getPath() });
 
                     await engine.lintFiles("*.js");
                 }, /Unexpected top-level property "overrides\[0\]\.ignorePatterns"/u);
@@ -5307,37 +5508,38 @@ describe("ESLint", () => {
 
     describe("'overrides[].files' adds lint targets", () => {
         const root = getFixturePath("cli-engine/additional-lint-targets");
-        let InMemoryESLint;
+
 
         describe("if { files: 'foo/*.txt', excludedFiles: '**/ignore.txt' } is present,", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.json": JSON.stringify({
-                            overrides: [
-                                {
-                                    files: "foo/*.txt",
-                                    excludedFiles: "**/ignore.txt"
-                                }
-                            ]
-                        }),
-                        "foo/nested/test.txt": "",
-                        "foo/test.js": "",
-                        "foo/test.txt": "",
-                        "foo/ignore.txt": "",
-                        "bar/test.js": "",
-                        "bar/test.txt": "",
-                        "bar/ignore.txt": "",
-                        "test.js": "",
-                        "test.txt": "",
-                        "ignore.txt": ""
-                    }
-                }).ESLint;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.json": JSON.stringify({
+                        overrides: [
+                            {
+                                files: "foo/*.txt",
+                                excludedFiles: "**/ignore.txt"
+                            }
+                        ]
+                    }),
+                    "foo/nested/test.txt": "",
+                    "foo/test.js": "",
+                    "foo/test.txt": "",
+                    "foo/ignore.txt": "",
+                    "bar/test.js": "",
+                    "bar/test.txt": "",
+                    "bar/ignore.txt": "",
+                    "test.js": "",
+                    "test.txt": "",
+                    "ignore.txt": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'lintFiles()' with a directory path should contain 'foo/test.txt'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
                 const filePaths = (await engine.lintFiles("."))
                     .map(r => r.filePath)
                     .sort();
@@ -5351,7 +5553,7 @@ describe("ESLint", () => {
             });
 
             it("'lintFiles()' with a glob pattern '*.js' should not contain 'foo/test.txt'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
                 const filePaths = (await engine.lintFiles("**/*.js"))
                     .map(r => r.filePath)
                     .sort();
@@ -5365,30 +5567,32 @@ describe("ESLint", () => {
         });
 
         describe("if { files: 'foo/**/*.txt' } is present,", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.json": JSON.stringify({
-                            overrides: [
-                                {
-                                    files: "foo/**/*.txt"
-                                }
-                            ]
-                        }),
-                        "foo/nested/test.txt": "",
-                        "foo/test.js": "",
-                        "foo/test.txt": "",
-                        "bar/test.js": "",
-                        "bar/test.txt": "",
-                        "test.js": "",
-                        "test.txt": ""
-                    }
-                }).ESLint;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.json": JSON.stringify({
+                        overrides: [
+                            {
+                                files: "foo/**/*.txt"
+                            }
+                        ]
+                    }),
+                    "foo/nested/test.txt": "",
+                    "foo/test.js": "",
+                    "foo/test.txt": "",
+                    "bar/test.js": "",
+                    "bar/test.txt": "",
+                    "test.js": "",
+                    "test.txt": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'lintFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
                 const filePaths = (await engine.lintFiles("."))
                     .map(r => r.filePath)
                     .sort();
@@ -5404,30 +5608,32 @@ describe("ESLint", () => {
         });
 
         describe("if { files: 'foo/**/*' } is present,", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        ".eslintrc.json": JSON.stringify({
-                            overrides: [
-                                {
-                                    files: "foo/**/*"
-                                }
-                            ]
-                        }),
-                        "foo/nested/test.txt": "",
-                        "foo/test.js": "",
-                        "foo/test.txt": "",
-                        "bar/test.js": "",
-                        "bar/test.txt": "",
-                        "test.js": "",
-                        "test.txt": ""
-                    }
-                }).ESLint;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    ".eslintrc.json": JSON.stringify({
+                        overrides: [
+                            {
+                                files: "foo/**/*"
+                            }
+                        ]
+                    }),
+                    "foo/nested/test.txt": "",
+                    "foo/test.js": "",
+                    "foo/test.txt": "",
+                    "bar/test.js": "",
+                    "bar/test.txt": "",
+                    "test.js": "",
+                    "test.txt": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'lintFiles()' with a directory path should NOT contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
                 const filePaths = (await engine.lintFiles("."))
                     .map(r => r.filePath)
                     .sort();
@@ -5441,33 +5647,35 @@ describe("ESLint", () => {
         });
 
         describe("if { files: 'foo/**/*.txt' } is present in a shareable config,", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-config-foo/index.js": `module.exports = ${JSON.stringify({
-                            overrides: [
-                                {
-                                    files: "foo/**/*.txt"
-                                }
-                            ]
-                        })}`,
-                        ".eslintrc.json": JSON.stringify({
-                            extends: "foo"
-                        }),
-                        "foo/nested/test.txt": "",
-                        "foo/test.js": "",
-                        "foo/test.txt": "",
-                        "bar/test.js": "",
-                        "bar/test.txt": "",
-                        "test.js": "",
-                        "test.txt": ""
-                    }
-                }).ESLint;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    "node_modules/eslint-config-foo/index.js": `module.exports = ${JSON.stringify({
+                        overrides: [
+                            {
+                                files: "foo/**/*.txt"
+                            }
+                        ]
+                    })}`,
+                    ".eslintrc.json": JSON.stringify({
+                        extends: "foo"
+                    }),
+                    "foo/nested/test.txt": "",
+                    "foo/test.js": "",
+                    "foo/test.txt": "",
+                    "bar/test.js": "",
+                    "bar/test.txt": "",
+                    "test.js": "",
+                    "test.txt": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'lintFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
                 const filePaths = (await engine.lintFiles("."))
                     .map(r => r.filePath)
                     .sort();
@@ -5483,35 +5691,37 @@ describe("ESLint", () => {
         });
 
         describe("if { files: 'foo/**/*.txt' } is present in a plugin config,", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-plugin-foo/index.js": `exports.configs = ${JSON.stringify({
-                            bar: {
-                                overrides: [
-                                    {
-                                        files: "foo/**/*.txt"
-                                    }
-                                ]
-                            }
-                        })}`,
-                        ".eslintrc.json": JSON.stringify({
-                            extends: "plugin:foo/bar"
-                        }),
-                        "foo/nested/test.txt": "",
-                        "foo/test.js": "",
-                        "foo/test.txt": "",
-                        "bar/test.js": "",
-                        "bar/test.txt": "",
-                        "test.js": "",
-                        "test.txt": ""
-                    }
-                }).ESLint;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    "node_modules/eslint-plugin-foo/index.js": `exports.configs = ${JSON.stringify({
+                        bar: {
+                            overrides: [
+                                {
+                                    files: "foo/**/*.txt"
+                                }
+                            ]
+                        }
+                    })}`,
+                    ".eslintrc.json": JSON.stringify({
+                        extends: "plugin:foo/bar"
+                    }),
+                    "foo/nested/test.txt": "",
+                    "foo/test.js": "",
+                    "foo/test.txt": "",
+                    "bar/test.js": "",
+                    "bar/test.txt": "",
+                    "test.js": "",
+                    "test.txt": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'lintFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => {
-                const engine = new InMemoryESLint();
+                const engine = new ESLint({ cwd: getPath() });
                 const filePaths = (await engine.lintFiles("."))
                     .map(r => r.filePath)
                     .sort();
@@ -5530,34 +5740,32 @@ describe("ESLint", () => {
     describe("'ignorePatterns', 'overrides[].files', and 'overrides[].excludedFiles' of the configuration that the '--config' option provided should be resolved from CWD.", () => {
         const root = getFixturePath("cli-engine/config-and-overrides-files");
 
-        /** @type {ESLint} */
-        let InMemoryESLint;
-
         describe("if { files: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/myconf/.eslintrc.json": JSON.stringify({
-                            overrides: [
-                                {
-                                    files: "foo/*.js",
-                                    rules: {
-                                        eqeqeq: "error"
-                                    }
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    "node_modules/myconf/.eslintrc.json": {
+                        overrides: [
+                            {
+                                files: "foo/*.js",
+                                rules: {
+                                    eqeqeq: "error"
                                 }
-                            ]
-                        }),
-                        "node_modules/myconf/foo/test.js": "a == b",
-                        "foo/test.js": "a == b"
-                    }
-                }).ESLint;
+                            }
+                        ]
+                    },
+                    "node_modules/myconf/foo/test.js": "a == b",
+                    "foo/test.js": "a == b"
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'lintFiles()' with 'foo/test.js' should use the override entry.", async () => {
-                const engine = new InMemoryESLint({
+                const engine = new ESLint({
                     overrideConfigFile: "node_modules/myconf/.eslintrc.json",
-                    cwd: root,
+                    cwd: getPath(),
                     ignore: false,
                     useEslintrc: false
                 });
@@ -5567,7 +5775,7 @@ describe("ESLint", () => {
                 assert.deepStrictEqual(results, [
                     {
                         errorCount: 1,
-                        filePath: path.join(root, "foo/test.js"),
+                        filePath: path.join(getPath(), "foo/test.js"),
                         fixableErrorCount: 0,
                         fixableWarningCount: 0,
                         messages: [
@@ -5591,7 +5799,7 @@ describe("ESLint", () => {
             });
 
             it("'lintFiles()' with 'node_modules/myconf/foo/test.js' should NOT use the override entry.", async () => {
-                const engine = new InMemoryESLint({
+                const engine = new ESLint({
                     overrideConfigFile: "node_modules/myconf/.eslintrc.json",
                     cwd: root,
                     ignore: false,
@@ -5603,7 +5811,7 @@ describe("ESLint", () => {
                 assert.deepStrictEqual(results, [
                     {
                         errorCount: 0,
-                        filePath: path.join(root, "node_modules/myconf/foo/test.js"),
+                        filePath: path.join(getPath(), "node_modules/myconf/foo/test.js"),
                         fixableErrorCount: 0,
                         fixableWarningCount: 0,
                         messages: [],
@@ -5615,29 +5823,30 @@ describe("ESLint", () => {
         });
 
         describe("if { files: '*', excludedFiles: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/myconf/.eslintrc.json": JSON.stringify({
-                            overrides: [
-                                {
-                                    files: "*",
-                                    excludedFiles: "foo/*.js",
-                                    rules: {
-                                        eqeqeq: "error"
-                                    }
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    "node_modules/myconf/.eslintrc.json": JSON.stringify({
+                        overrides: [
+                            {
+                                files: "*",
+                                excludedFiles: "foo/*.js",
+                                rules: {
+                                    eqeqeq: "error"
                                 }
-                            ]
-                        }),
-                        "node_modules/myconf/foo/test.js": "a == b",
-                        "foo/test.js": "a == b"
-                    }
-                }).ESLint;
+                            }
+                        ]
+                    }),
+                    "node_modules/myconf/foo/test.js": "a == b",
+                    "foo/test.js": "a == b"
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'lintFiles()' with 'foo/test.js' should NOT use the override entry.", async () => {
-                const engine = new InMemoryESLint({
+                const engine = new ESLint({
                     overrideConfigFile: "node_modules/myconf/.eslintrc.json",
                     cwd: root,
                     ignore: false,
@@ -5649,7 +5858,7 @@ describe("ESLint", () => {
                 assert.deepStrictEqual(results, [
                     {
                         errorCount: 0,
-                        filePath: path.join(root, "foo/test.js"),
+                        filePath: path.join(getPath(), "foo/test.js"),
                         fixableErrorCount: 0,
                         fixableWarningCount: 0,
                         messages: [],
@@ -5660,7 +5869,7 @@ describe("ESLint", () => {
             });
 
             it("'lintFiles()' with 'node_modules/myconf/foo/test.js' should use the override entry.", async () => {
-                const engine = new InMemoryESLint({
+                const engine = new ESLint({
                     overrideConfigFile: "node_modules/myconf/.eslintrc.json",
                     cwd: root,
                     ignore: false,
@@ -5672,7 +5881,7 @@ describe("ESLint", () => {
                 assert.deepStrictEqual(results, [
                     {
                         errorCount: 1,
-                        filePath: path.join(root, "node_modules/myconf/foo/test.js"),
+                        filePath: path.join(getPath(), "node_modules/myconf/foo/test.js"),
                         fixableErrorCount: 0,
                         fixableWarningCount: 0,
                         messages: [
@@ -5697,26 +5906,27 @@ describe("ESLint", () => {
         });
 
         describe("if { ignorePatterns: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/myconf/.eslintrc.json": JSON.stringify({
-                            ignorePatterns: ["!/node_modules/myconf", "foo/*.js"],
-                            rules: {
-                                eqeqeq: "error"
-                            }
-                        }),
-                        "node_modules/myconf/foo/test.js": "a == b",
-                        "foo/test.js": "a == b"
-                    }
-                }).ESLint;
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: root,
+                files: {
+                    "node_modules/myconf/.eslintrc.json": JSON.stringify({
+                        ignorePatterns: ["!/node_modules/myconf", "foo/*.js"],
+                        rules: {
+                            eqeqeq: "error"
+                        }
+                    }),
+                    "node_modules/myconf/foo/test.js": "a == b",
+                    "foo/test.js": "a == b"
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'lintFiles()' with '**/*.js' should iterate 'node_modules/myconf/foo/test.js' but not 'foo/test.js'.", async () => {
-                const engine = new InMemoryESLint({
+                const engine = new ESLint({
                     overrideConfigFile: "node_modules/myconf/.eslintrc.json",
-                    cwd: root,
+                    cwd: getPath(),
                     useEslintrc: false
                 });
                 const files = (await engine.lintFiles("**/*.js"))
@@ -5732,14 +5942,7 @@ describe("ESLint", () => {
 
     describe("plugin conflicts", () => {
         let uid = 0;
-        let root = "";
-
-        beforeEach(() => {
-            root = getFixturePath(`eslint/plugin-conflicts-${++uid}`);
-        });
-
-        /** @type {typeof ESLint} */
-        let InMemoryESLint;
+        const root = getFixturePath("cli-engine/plugin-conflicts-");
 
         /**
          * Verify thrown errors.
@@ -5761,110 +5964,118 @@ describe("ESLint", () => {
         }
 
         describe("between a config file and linear extendees.", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-plugin-foo/index.js": "",
-                        "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": "",
-                        "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
-                            extends: ["two"],
-                            plugins: ["foo"]
-                        })}`,
-                        "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": "",
-                        "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({
-                            plugins: ["foo"]
-                        })}`,
-                        ".eslintrc.json": JSON.stringify({
-                            extends: ["one"],
-                            plugins: ["foo"]
-                        }),
-                        "test.js": ""
-                    }
-                }).ESLint;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: `${root}${++uid}`,
+                files: {
+                    "node_modules/eslint-plugin-foo/index.js": "",
+                    "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": "",
+                    "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
+                        extends: ["two"],
+                        plugins: ["foo"]
+                    })}`,
+                    "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": "",
+                    "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({
+                        plugins: ["foo"]
+                    })}`,
+                    ".eslintrc.json": JSON.stringify({
+                        extends: ["one"],
+                        plugins: ["foo"]
+                    }),
+                    "test.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file.)", async () => {
-                const engine = new InMemoryESLint({ cwd: root });
+                const engine = new ESLint({ cwd: getPath() });
 
                 await engine.lintFiles("test.js");
             });
         });
 
         describe("between a config file and same-depth extendees.", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-plugin-foo/index.js": "",
-                        "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": "",
-                        "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
-                            plugins: ["foo"]
-                        })}`,
-                        "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": "",
-                        "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({
-                            plugins: ["foo"]
-                        })}`,
-                        ".eslintrc.json": JSON.stringify({
-                            extends: ["one", "two"],
-                            plugins: ["foo"]
-                        }),
-                        "test.js": ""
-                    }
-                }).ESLint;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: `${root}${++uid}`,
+                files: {
+                    "node_modules/eslint-plugin-foo/index.js": "",
+                    "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": "",
+                    "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({
+                        plugins: ["foo"]
+                    })}`,
+                    "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": "",
+                    "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({
+                        plugins: ["foo"]
+                    })}`,
+                    ".eslintrc.json": JSON.stringify({
+                        extends: ["one", "two"],
+                        plugins: ["foo"]
+                    }),
+                    "test.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file.)", async () => {
-                const engine = new InMemoryESLint({ cwd: root });
+                const engine = new ESLint({ cwd: getPath() });
 
                 await engine.lintFiles("test.js");
             });
         });
 
         describe("between two config files in different directories, with single node_modules.", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-plugin-foo/index.js": "",
-                        ".eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        "subdir/.eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        "subdir/test.js": ""
-                    }
-                }).ESLint;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: `${root}${++uid}`,
+                files: {
+                    "node_modules/eslint-plugin-foo/index.js": "",
+                    ".eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    "subdir/.eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    "subdir/test.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files, but node_modules directory is unique.)", async () => {
-                const engine = new InMemoryESLint({ cwd: root });
+                const engine = new ESLint({ cwd: getPath() });
 
                 await engine.lintFiles("subdir/test.js");
             });
         });
 
         describe("between two config files in different directories, with multiple node_modules.", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-plugin-foo/index.js": "",
-                        ".eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        "subdir/node_modules/eslint-plugin-foo/index.js": "",
-                        "subdir/.eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        "subdir/test.js": ""
-                    }
-                }).ESLint;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: `${root}${++uid}`,
+                files: {
+                    "node_modules/eslint-plugin-foo/index.js": "",
+                    ".eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    "subdir/node_modules/eslint-plugin-foo/index.js": "",
+                    "subdir/.eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    "subdir/test.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'lintFiles()' should throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files.)", async () => {
-                const engine = new InMemoryESLint({ cwd: root });
+                const engine = new ESLint({ cwd: getPath() });
 
                 await assertThrows(
                     () => engine.lintFiles("subdir/test.js"),
@@ -5875,11 +6086,11 @@ describe("ESLint", () => {
                             pluginId: "foo",
                             plugins: [
                                 {
-                                    filePath: path.join(root, "subdir/node_modules/eslint-plugin-foo/index.js"),
+                                    filePath: path.join(getPath(), "subdir/node_modules/eslint-plugin-foo/index.js"),
                                     importerName: `subdir${path.sep}.eslintrc.json`
                                 },
                                 {
-                                    filePath: path.join(root, "node_modules/eslint-plugin-foo/index.js"),
+                                    filePath: path.join(getPath(), "node_modules/eslint-plugin-foo/index.js"),
                                     importerName: ".eslintrc.json"
                                 }
                             ]
@@ -5890,25 +6101,27 @@ describe("ESLint", () => {
         });
 
         describe("between '--config' option and a regular config file, with single node_modules.", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-plugin-foo/index.js": "",
-                        "node_modules/mine/.eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        ".eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        "test.js": ""
-                    }
-                }).ESLint;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: `${root}${++uid}`,
+                files: {
+                    "node_modules/eslint-plugin-foo/index.js": "",
+                    "node_modules/mine/.eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    ".eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    "test.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files, but node_modules directory is unique.)", async () => {
-                const engine = new InMemoryESLint({
-                    cwd: root,
+                const engine = new ESLint({
+                    cwd: getPath(),
                     overrideConfigFile: "node_modules/mine/.eslintrc.json"
                 });
 
@@ -5917,26 +6130,28 @@ describe("ESLint", () => {
         });
 
         describe("between '--config' option and a regular config file, with multiple node_modules.", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-plugin-foo/index.js": "",
-                        "node_modules/mine/node_modules/eslint-plugin-foo/index.js": "",
-                        "node_modules/mine/.eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        ".eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        "test.js": ""
-                    }
-                }).ESLint;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: `${root}${++uid}`,
+                files: {
+                    "node_modules/eslint-plugin-foo/index.js": "",
+                    "node_modules/mine/node_modules/eslint-plugin-foo/index.js": "",
+                    "node_modules/mine/.eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    ".eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    "test.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'lintFiles()' should throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files.)", async () => {
-                const engine = new InMemoryESLint({
-                    cwd: root,
+                const engine = new ESLint({
+                    cwd: getPath(),
                     overrideConfigFile: "node_modules/mine/.eslintrc.json"
                 });
 
@@ -5949,11 +6164,11 @@ describe("ESLint", () => {
                             pluginId: "foo",
                             plugins: [
                                 {
-                                    filePath: path.join(root, "node_modules/mine/node_modules/eslint-plugin-foo/index.js"),
+                                    filePath: path.join(getPath(), "node_modules/mine/node_modules/eslint-plugin-foo/index.js"),
                                     importerName: "--config"
                                 },
                                 {
-                                    filePath: path.join(root, "node_modules/eslint-plugin-foo/index.js"),
+                                    filePath: path.join(getPath(), "node_modules/eslint-plugin-foo/index.js"),
                                     importerName: ".eslintrc.json"
                                 }
                             ]
@@ -5964,22 +6179,25 @@ describe("ESLint", () => {
         });
 
         describe("between '--plugin' option and a regular config file, with single node_modules.", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-plugin-foo/index.js": "",
-                        "subdir/.eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        "subdir/test.js": ""
-                    }
-                }).ESLint;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: `${root}${++uid}`,
+                files: {
+                    "node_modules/eslint-plugin-foo/index.js": "",
+                    "subdir/.eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    "subdir/test.js": ""
+                }
             });
 
+
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from both CWD and the base directory of the entry config file, but node_modules directory is unique.)", async () => {
-                const engine = new InMemoryESLint({
-                    cwd: root,
+                const engine = new ESLint({
+                    cwd: getPath(),
                     overrideConfig: { plugins: ["foo"] }
                 });
 
@@ -5988,23 +6206,25 @@ describe("ESLint", () => {
         });
 
         describe("between '--plugin' option and a regular config file, with multiple node_modules.", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-plugin-foo/index.js": "",
-                        "subdir/node_modules/eslint-plugin-foo/index.js": "",
-                        "subdir/.eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        "subdir/test.js": ""
-                    }
-                }).ESLint;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: `${root}${++uid}`,
+                files: {
+                    "node_modules/eslint-plugin-foo/index.js": "",
+                    "subdir/node_modules/eslint-plugin-foo/index.js": "",
+                    "subdir/.eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    "subdir/test.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'lintFiles()' should throw plugin-conflict error. (Load the plugin from both CWD and the base directory of the entry config file.)", async () => {
-                const engine = new InMemoryESLint({
-                    cwd: root,
+                const engine = new ESLint({
+                    cwd: getPath(),
                     overrideConfig: { plugins: ["foo"] }
                 });
 
@@ -6017,11 +6237,11 @@ describe("ESLint", () => {
                             pluginId: "foo",
                             plugins: [
                                 {
-                                    filePath: path.join(root, "node_modules/eslint-plugin-foo/index.js"),
+                                    filePath: path.join(getPath(), "node_modules/eslint-plugin-foo/index.js"),
                                     importerName: "CLIOptions"
                                 },
                                 {
-                                    filePath: path.join(root, "subdir/node_modules/eslint-plugin-foo/index.js"),
+                                    filePath: path.join(getPath(), "subdir/node_modules/eslint-plugin-foo/index.js"),
                                     importerName: `subdir${path.sep}.eslintrc.json`
                                 }
                             ]
@@ -6032,27 +6252,29 @@ describe("ESLint", () => {
         });
 
         describe("'--resolve-plugins-relative-to' option overrides the location that ESLint load plugins from.", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "node_modules/eslint-plugin-foo/index.js": "",
-                        ".eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        "subdir/node_modules/eslint-plugin-foo/index.js": "",
-                        "subdir/.eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        "subdir/test.js": ""
-                    }
-                }).ESLint;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: `${root}${++uid}`,
+                files: {
+                    "node_modules/eslint-plugin-foo/index.js": "",
+                    ".eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    "subdir/node_modules/eslint-plugin-foo/index.js": "",
+                    "subdir/.eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    "subdir/test.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from '--resolve-plugins-relative-to'.)", async () => {
-                const engine = new InMemoryESLint({
-                    cwd: root,
-                    resolvePluginsRelativeTo: root
+                const engine = new ESLint({
+                    cwd: getPath(),
+                    resolvePluginsRelativeTo: getPath()
                 });
 
                 await engine.lintFiles("subdir/test.js");
@@ -6060,26 +6282,28 @@ describe("ESLint", () => {
         });
 
         describe("between two config files with different target files.", () => {
-            beforeEach(() => {
-                InMemoryESLint = defineESLintWithInMemoryFileSystem({
-                    cwd: () => root,
-                    files: {
-                        "one/node_modules/eslint-plugin-foo/index.js": "",
-                        "one/.eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        "one/test.js": "",
-                        "two/node_modules/eslint-plugin-foo/index.js": "",
-                        "two/.eslintrc.json": JSON.stringify({
-                            plugins: ["foo"]
-                        }),
-                        "two/test.js": ""
-                    }
-                }).ESLint;
+
+            const { prepare, cleanup, getPath } = createCustomTeardown({
+                cwd: `${root}${++uid}`,
+                files: {
+                    "one/node_modules/eslint-plugin-foo/index.js": "",
+                    "one/.eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    "one/test.js": "",
+                    "two/node_modules/eslint-plugin-foo/index.js": "",
+                    "two/.eslintrc.json": JSON.stringify({
+                        plugins: ["foo"]
+                    }),
+                    "two/test.js": ""
+                }
             });
 
+            beforeEach(prepare);
+            afterEach(cleanup);
+
             it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file for each target file. Not related to each other.)", async () => {
-                const engine = new InMemoryESLint({ cwd: root });
+                const engine = new ESLint({ cwd: getPath() });
                 const results = await engine.lintFiles("*/test.js");
 
                 assert.strictEqual(results.length, 2);
index 69556b4b5b618b5f7fedfe43e1819c78c18e429c..e9fe62e2c305bcb8d439907c13fcbe1cf38587d2 100644 (file)
@@ -9,10 +9,8 @@
 //------------------------------------------------------------------------------
 
 const assert = require("chai").assert,
-    leche = require("leche"),
     sinon = require("sinon"),
     path = require("path"),
-    fs = require("fs"),
     yaml = require("js-yaml"),
     espree = require("espree"),
     ConfigFile = require("../../../lib/init/config-file"),
@@ -59,15 +57,17 @@ describe("ConfigFile", () => {
             sinon.verifyAndRestore();
         });
 
-        leche.withData([
+        [
             ["JavaScript", "foo.js", espree.parse],
             ["JSON", "bar.json", JSON.parse],
             ["YAML", "foo.yaml", yaml.safeLoad],
             ["YML", "foo.yml", yaml.safeLoad]
-        ], (fileType, filename, validate) => {
+        ].forEach(([fileType, filename, validate]) => {
 
             it(`should write a file through fs when a ${fileType} path is passed`, () => {
-                const fakeFS = leche.fake(fs);
+                const fakeFS = {
+                    writeFileSync: () => {}
+                };
 
                 sinon.mock(fakeFS).expects("writeFileSync").withExactArgs(
                     filename,
@@ -83,7 +83,9 @@ describe("ConfigFile", () => {
             });
 
             it("should include a newline character at EOF", () => {
-                const fakeFS = leche.fake(fs);
+                const fakeFS = {
+                    writeFileSync: () => {}
+                };
 
                 sinon.mock(fakeFS).expects("writeFileSync").withExactArgs(
                     filename,
@@ -100,7 +102,9 @@ describe("ConfigFile", () => {
         });
 
         it("should make sure js config files match linting rules", () => {
-            const fakeFS = leche.fake(fs);
+            const fakeFS = {
+                writeFileSync: () => {}
+            };
 
             const singleQuoteConfig = {
                 rules: {
@@ -122,7 +126,9 @@ describe("ConfigFile", () => {
         });
 
         it("should still write a js config file even if linting fails", () => {
-            const fakeFS = leche.fake(fs);
+            const fakeFS = {
+                writeFileSync: () => {}
+            };
             const fakeCLIEngine = sinon.mock().withExactArgs(sinon.match({
                 baseConfig: config,
                 fix: true,
index 945e1854fba1b05de7ba29dbe874a9bae7835c83..d607ccff52f4535b840dbe90f38f102667bd9fa9 100644 (file)
@@ -136,7 +136,7 @@ describe("configInitializer", () => {
                 assert.deepStrictEqual(config.rules.quotes, ["error", "single"]);
                 assert.deepStrictEqual(config.rules["linebreak-style"], ["error", "unix"]);
                 assert.deepStrictEqual(config.rules.semi, ["error", "always"]);
-                assert.strictEqual(config.env.es2020, true);
+                assert.strictEqual(config.env.es2021, true);
                 assert.strictEqual(config.parserOptions.ecmaVersion, espree.latestEcmaVersion);
                 assert.strictEqual(config.parserOptions.sourceType, "module");
                 assert.strictEqual(config.env.browser, true);
@@ -174,7 +174,7 @@ describe("configInitializer", () => {
 
                 assert.strictEqual(config.parser, "@typescript-eslint/parser");
                 assert.deepStrictEqual(config.plugins, ["@typescript-eslint"]);
-                assert.deepStrictEqual(config.extends, ["eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended"]);
+                assert.deepStrictEqual(config.extends, ["eslint:recommended", "plugin:@typescript-eslint/recommended"]);
             });
 
             it("should enable typescript parser and plugin with vue", () => {
@@ -182,7 +182,7 @@ describe("configInitializer", () => {
                 answers.typescript = true;
                 const config = init.processAnswers(answers);
 
-                assert.deepStrictEqual(config.extends, ["eslint:recommended", "plugin:vue/essential", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended"]);
+                assert.deepStrictEqual(config.extends, ["eslint:recommended", "plugin:vue/essential", "plugin:@typescript-eslint/recommended"]);
                 assert.strictEqual(config.parserOptions.parser, "@typescript-eslint/parser");
                 assert.deepStrictEqual(config.plugins, ["vue", "@typescript-eslint"]);
             });
index ec516c54a0259fd04aeb8d254096f87ca5da1083..c5d0ecf267f4c01403ac8b837933041c6732b9eb 100644 (file)
@@ -557,7 +557,7 @@ describe("CodePathAnalyzer", () => {
                     }
                 }));
                 const messages = linter.verify(source, {
-                    parserOptions: { ecmaVersion: 2020 },
+                    parserOptions: { ecmaVersion: 2021 },
                     rules: { test: 2 }
                 });
 
index a79400cf7e401cbd519fa30da6621aa1c122fdf3..4d137b578ba70b94442c68bff6456c202eb2062b 100644 (file)
@@ -553,6 +553,224 @@ describe("createReportTranslator", () => {
                 }
             );
         });
+
+        it("should remove the whole suggestion if 'fix' function returned `null`.", () => {
+            const reportDescriptor = {
+                node,
+                loc: location,
+                message,
+                suggest: [{
+                    desc: "A suggestion for the issue",
+                    fix: () => null
+                }]
+            };
+
+            assert.deepStrictEqual(
+                translateReport(reportDescriptor),
+                {
+                    ruleId: "foo-rule",
+                    severity: 2,
+                    message: "foo",
+                    line: 2,
+                    column: 1,
+                    nodeType: "ExpressionStatement"
+                }
+            );
+        });
+
+        it("should remove the whole suggestion if 'fix' function returned an empty array.", () => {
+            const reportDescriptor = {
+                node,
+                loc: location,
+                message,
+                suggest: [{
+                    desc: "A suggestion for the issue",
+                    fix: () => []
+                }]
+            };
+
+            assert.deepStrictEqual(
+                translateReport(reportDescriptor),
+                {
+                    ruleId: "foo-rule",
+                    severity: 2,
+                    message: "foo",
+                    line: 2,
+                    column: 1,
+                    nodeType: "ExpressionStatement"
+                }
+            );
+        });
+
+        it("should remove the whole suggestion if 'fix' function returned an empty sequence.", () => {
+            const reportDescriptor = {
+                node,
+                loc: location,
+                message,
+                suggest: [{
+                    desc: "A suggestion for the issue",
+                    *fix() {}
+                }]
+            };
+
+            assert.deepStrictEqual(
+                translateReport(reportDescriptor),
+                {
+                    ruleId: "foo-rule",
+                    severity: 2,
+                    message: "foo",
+                    line: 2,
+                    column: 1,
+                    nodeType: "ExpressionStatement"
+                }
+            );
+        });
+
+        // This isn't offically supported, but autofix works the same way
+        it("should remove the whole suggestion if 'fix' function didn't return anything.", () => {
+            const reportDescriptor = {
+                node,
+                loc: location,
+                message,
+                suggest: [{
+                    desc: "A suggestion for the issue",
+                    fix() {}
+                }]
+            };
+
+            assert.deepStrictEqual(
+                translateReport(reportDescriptor),
+                {
+                    ruleId: "foo-rule",
+                    severity: 2,
+                    message: "foo",
+                    line: 2,
+                    column: 1,
+                    nodeType: "ExpressionStatement"
+                }
+            );
+        });
+
+        it("should keep suggestion before a removed suggestion.", () => {
+            const reportDescriptor = {
+                node,
+                loc: location,
+                message,
+                suggest: [{
+                    desc: "Suggestion with a fix",
+                    fix: () => ({ range: [1, 2], text: "foo" })
+                }, {
+                    desc: "Suggestion without a fix",
+                    fix: () => null
+                }]
+            };
+
+            assert.deepStrictEqual(
+                translateReport(reportDescriptor),
+                {
+                    ruleId: "foo-rule",
+                    severity: 2,
+                    message: "foo",
+                    line: 2,
+                    column: 1,
+                    nodeType: "ExpressionStatement",
+                    suggestions: [{
+                        desc: "Suggestion with a fix",
+                        fix: { range: [1, 2], text: "foo" }
+                    }]
+                }
+            );
+        });
+
+        it("should keep suggestion after a removed suggestion.", () => {
+            const reportDescriptor = {
+                node,
+                loc: location,
+                message,
+                suggest: [{
+                    desc: "Suggestion without a fix",
+                    fix: () => null
+                }, {
+                    desc: "Suggestion with a fix",
+                    fix: () => ({ range: [1, 2], text: "foo" })
+                }]
+            };
+
+            assert.deepStrictEqual(
+                translateReport(reportDescriptor),
+                {
+                    ruleId: "foo-rule",
+                    severity: 2,
+                    message: "foo",
+                    line: 2,
+                    column: 1,
+                    nodeType: "ExpressionStatement",
+                    suggestions: [{
+                        desc: "Suggestion with a fix",
+                        fix: { range: [1, 2], text: "foo" }
+                    }]
+                }
+            );
+        });
+
+        it("should remove multiple suggestions that didn't provide a fix and keep those that did.", () => {
+            const reportDescriptor = {
+                node,
+                loc: location,
+                message,
+                suggest: [{
+                    desc: "Keep #1",
+                    fix: () => ({ range: [1, 2], text: "foo" })
+                }, {
+                    desc: "Remove #1",
+                    fix() {
+                        return null;
+                    }
+                }, {
+                    desc: "Keep #2",
+                    fix: () => ({ range: [1, 2], text: "bar" })
+                }, {
+                    desc: "Remove #2",
+                    fix() {
+                        return [];
+                    }
+                }, {
+                    desc: "Keep #3",
+                    fix: () => ({ range: [1, 2], text: "baz" })
+                }, {
+                    desc: "Remove #3",
+                    *fix() {}
+                }, {
+                    desc: "Keep #4",
+                    fix: () => ({ range: [1, 2], text: "quux" })
+                }]
+            };
+
+            assert.deepStrictEqual(
+                translateReport(reportDescriptor),
+                {
+                    ruleId: "foo-rule",
+                    severity: 2,
+                    message: "foo",
+                    line: 2,
+                    column: 1,
+                    nodeType: "ExpressionStatement",
+                    suggestions: [{
+                        desc: "Keep #1",
+                        fix: { range: [1, 2], text: "foo" }
+                    }, {
+                        desc: "Keep #2",
+                        fix: { range: [1, 2], text: "bar" }
+                    }, {
+                        desc: "Keep #3",
+                        fix: { range: [1, 2], text: "baz" }
+                    }, {
+                        desc: "Keep #4",
+                        fix: { range: [1, 2], text: "quux" }
+                    }]
+                }
+            );
+        });
     });
 
     describe("message interpolation", () => {
index fedc237d64ce2dc89d1b14a251301b0b953cf765..3f2393621b6b85f439292e363f4aa6694d9dc3f8 100644 (file)
@@ -304,6 +304,10 @@ describe("RuleTester", () => {
 
     it("should use strict equality to compare output", () => {
         const replaceProgramWith5Rule = {
+            meta: {
+                fixable: "code"
+            },
+
             create: context => ({
                 Program(node) {
                     context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") });
@@ -463,6 +467,37 @@ describe("RuleTester", () => {
         }, expectedErrorMessage);
     });
 
+    it("should throw error for empty error array", () => {
+        assert.throws(() => {
+            ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
+                valid: [],
+                invalid: [{
+                    code: "var foo;",
+                    errors: []
+                }]
+            });
+        }, /Invalid cases must have at least one error/u);
+    });
+
+    it("should throw error for errors : 0", () => {
+        assert.throws(() => {
+            ruleTester.run(
+                "suggestions-messageIds",
+                require("../../fixtures/testers/rule-tester/suggestions")
+                    .withMessageIds,
+                {
+                    valid: [],
+                    invalid: [
+                        {
+                            code: "var foo;",
+                            errors: 0
+                        }
+                    ]
+                }
+            );
+        }, /Invalid cases must have 'error' value greater than 0/u);
+    });
+
     it("should not skip column assertion if column is a falsy value", () => {
         assert.throws(() => {
             ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
@@ -732,7 +767,7 @@ describe("RuleTester", () => {
                 {
                     code: "eval(foo)",
                     parser: require.resolve("esprima"),
-                    errors: [{}]
+                    errors: [{ line: 1 }]
                 }
             ]
         });
@@ -1206,6 +1241,73 @@ describe("RuleTester", () => {
         }, "Error must specify 'messageId' if 'data' is used.");
     });
 
+    // fixable rules with or without `meta` property
+    it("should not throw an error if a rule that has `meta.fixable` produces fixes", () => {
+        const replaceProgramWith5Rule = {
+            meta: {
+                fixable: "code"
+            },
+            create(context) {
+                return {
+                    Program(node) {
+                        context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") });
+                    }
+                };
+            }
+        };
+
+        ruleTester.run("replaceProgramWith5", replaceProgramWith5Rule, {
+            valid: [],
+            invalid: [
+                { code: "var foo = bar;", output: "5", errors: 1 }
+            ]
+        });
+    });
+    it("should throw an error if a new-format rule that doesn't have `meta` produces fixes", () => {
+        const replaceProgramWith5Rule = {
+            create(context) {
+                return {
+                    Program(node) {
+                        context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") });
+                    }
+                };
+            }
+        };
+
+        assert.throws(() => {
+            ruleTester.run("replaceProgramWith5", replaceProgramWith5Rule, {
+                valid: [],
+                invalid: [
+                    { code: "var foo = bar;", output: "5", errors: 1 }
+                ]
+            });
+        }, "Fixable rules should export a `meta.fixable` property.");
+    });
+    it("should throw an error if a legacy-format rule produces fixes", () => {
+
+        /**
+         * Legacy-format rule (a function instead of an object with `create` method).
+         * @param {RuleContext} context The ESLint rule context object.
+         * @returns {Object} Listeners.
+         */
+        function replaceProgramWith5Rule(context) {
+            return {
+                Program(node) {
+                    context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") });
+                }
+            };
+        }
+
+        assert.throws(() => {
+            ruleTester.run("replaceProgramWith5", replaceProgramWith5Rule, {
+                valid: [],
+                invalid: [
+                    { code: "var foo = bar;", output: "5", errors: 1 }
+                ]
+            });
+        }, "Fixable rules should export a `meta.fixable` property.");
+    });
+
     describe("suggestions", () => {
         it("should pass with valid suggestions (tested using desc)", () => {
             ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
@@ -1893,4 +1995,5 @@ describe("RuleTester", () => {
         });
 
     });
+
 });
index e8f143ada4c79b704b82d107322829ec11ae7818..c6341326647460ffc00cf165763c8f12d48522a3 100644 (file)
@@ -1087,6 +1087,46 @@ ruleTester.run("accessor-pairs", rule, {
             code: "Object.create(null, {foo: {set: function(value) {}}});",
             errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }]
         },
+        {
+            code: "var o = {d: 1};\n Object?.defineProperty(o, 'c', \n{set: function(value) {\n val = value; \n} \n});",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }]
+        },
+        {
+            code: "Reflect?.defineProperty(obj, 'foo', {set: function(value) {}});",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }]
+        },
+        {
+            code: "Object?.defineProperties(obj, {foo: {set: function(value) {}}});",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }]
+        },
+        {
+            code: "Object?.create(null, {foo: {set: function(value) {}}});",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }]
+        },
+        {
+            code: "var o = {d: 1};\n (Object?.defineProperty)(o, 'c', \n{set: function(value) {\n val = value; \n} \n});",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }]
+        },
+        {
+            code: "(Reflect?.defineProperty)(obj, 'foo', {set: function(value) {}});",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }]
+        },
+        {
+            code: "(Object?.defineProperties)(obj, {foo: {set: function(value) {}}});",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }]
+        },
+        {
+            code: "(Object?.create)(null, {foo: {set: function(value) {}}});",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }]
+        },
 
         //------------------------------------------------------------------------------
         // Classes
index 24b40cb5204365f2eb5e420dd75927a204323736..6d6a4d86242c9b6c3b96e9fb9268a0d5a8bccd2d 100644 (file)
@@ -115,90 +115,91 @@ ruleTester.run("array-callback-return", rule, {
     ],
     invalid: [
 
-        { code: "Array.from(x, function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
-        { code: "Array.from(x, function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
-        { code: "Int32Array.from(x, function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
-        { code: "Int32Array.from(x, function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
-        { code: "foo.every(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
-        { code: "foo.every(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
-        { code: "foo.filter(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
-        { code: "foo.filter(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
-        { code: "foo.find(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
-        { code: "foo.find(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
-        { code: "foo.findIndex(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
-        { code: "foo.findIndex(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
-        { code: "foo.flatMap(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
-        { code: "foo.flatMap(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
-        { code: "foo.map(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
-        { code: "foo.map(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
-        { code: "foo.reduce(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
-        { code: "foo.reduce(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
-        { code: "foo.reduceRight(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
-        { code: "foo.reduceRight(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
-        { code: "foo.some(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
-        { code: "foo.some(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
-        { code: "foo.sort(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
-        { code: "foo.sort(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
-        { code: "foo.bar.baz.every(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
-        { code: "foo.bar.baz.every(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
-        { code: "foo[\"every\"](function() {})", errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
-        { code: "foo[\"every\"](function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
-        { code: "foo[`every`](function() {})", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
-        { code: "foo[`every`](function foo() {})", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
-        { code: "foo.every(() => {})", parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Expected to return a value in arrow function.", column: 14 }] },
-        { code: "foo.every(function() { if (a) return true; })", errors: [{ message: "Expected to return a value at the end of function.", column: 11 }] },
-        { code: "foo.every(function cb() { if (a) return true; })", errors: [{ message: "Expected to return a value at the end of function 'cb'.", column: 11 }] },
-        { code: "foo.every(function() { switch (a) { case 0: break; default: return true; } })", errors: [{ messageId: "expectedAtEnd", data: { name: "function" } }] },
-        { code: "foo.every(function foo() { switch (a) { case 0: break; default: return true; } })", errors: [{ messageId: "expectedAtEnd", data: { name: "function 'foo'" } }] },
-        { code: "foo.every(function() { try { bar(); } catch (err) { return true; } })", errors: [{ messageId: "expectedAtEnd", data: { name: "function" } }] },
-        { code: "foo.every(function foo() { try { bar(); } catch (err) { return true; } })", errors: [{ messageId: "expectedAtEnd", data: { name: "function 'foo'" } }] },
-        { code: "foo.every(function() { return; })", errors: [{ messageId: "expectedReturnValue", data: { name: "Function" } }] },
-        { code: "foo.every(function foo() { return; })", errors: [{ messageId: "expectedReturnValue", data: { name: "Function 'foo'" } }] },
-        { code: "foo.every(function() { if (a) return; })", errors: ["Expected to return a value at the end of function.", { messageId: "expectedReturnValue", data: { name: "Function" } }] },
-        { code: "foo.every(function foo() { if (a) return; })", errors: ["Expected to return a value at the end of function 'foo'.", { messageId: "expectedReturnValue", data: { name: "Function 'foo'" } }] },
-        { code: "foo.every(function() { if (a) return; else return; })", errors: [{ messageId: "expectedReturnValue", data: { name: "Function" } }, { messageId: "expectedReturnValue", data: { name: "Function" } }] },
-        { code: "foo.every(function foo() { if (a) return; else return; })", errors: [{ messageId: "expectedReturnValue", data: { name: "Function 'foo'" } }, { messageId: "expectedReturnValue", data: { name: "Function 'foo'" } }] },
-        { code: "foo.every(cb || function() {})", errors: ["Expected to return a value in function."] },
-        { code: "foo.every(cb || function foo() {})", errors: ["Expected to return a value in function 'foo'."] },
-        { code: "foo.every(a ? function() {} : function() {})", errors: ["Expected to return a value in function.", "Expected to return a value in function."] },
-        { code: "foo.every(a ? function foo() {} : function bar() {})", errors: ["Expected to return a value in function 'foo'.", "Expected to return a value in function 'bar'."] },
-        { code: "foo.every(function(){ return function() {}; }())", errors: [{ message: "Expected to return a value in function.", column: 30 }] },
-        { code: "foo.every(function(){ return function foo() {}; }())", errors: [{ message: "Expected to return a value in function 'foo'.", column: 30 }] },
-        { code: "foo.every(() => {})", options: [{ allowImplicit: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Expected to return a value in arrow function." }] },
-        { code: "foo.every(() => {})", options: [{ allowImplicit: true }], parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Expected to return a value in arrow function." }] },
+        { code: "Array.from(x, function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.from" } }] },
+        { code: "Array.from(x, function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.from" } }] },
+        { code: "Int32Array.from(x, function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.from" } }] },
+        { code: "Int32Array.from(x, function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.from" } }] },
+        { code: "foo.every(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] },
+        { code: "foo.every(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] },
+        { code: "foo.filter(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.filter" } }] },
+        { code: "foo.filter(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.filter" } }] },
+        { code: "foo.find(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.find" } }] },
+        { code: "foo.find(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.find" } }] },
+        { code: "foo.findIndex(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.findIndex" } }] },
+        { code: "foo.findIndex(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.findIndex" } }] },
+        { code: "foo.flatMap(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.flatMap" } }] },
+        { code: "foo.flatMap(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.flatMap" } }] },
+        { code: "foo.map(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.map" } }] },
+        { code: "foo.map(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.map" } }] },
+        { code: "foo.reduce(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.reduce" } }] },
+        { code: "foo.reduce(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.reduce" } }] },
+        { code: "foo.reduceRight(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.reduceRight" } }] },
+        { code: "foo.reduceRight(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.reduceRight" } }] },
+        { code: "foo.some(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.some" } }] },
+        { code: "foo.some(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.some" } }] },
+        { code: "foo.sort(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.sort" } }] },
+        { code: "foo.sort(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.sort" } }] },
+        { code: "foo.bar.baz.every(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] },
+        { code: "foo.bar.baz.every(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] },
+        { code: "foo[\"every\"](function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] },
+        { code: "foo[\"every\"](function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] },
+        { code: "foo[`every`](function() {})", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] },
+        { code: "foo[`every`](function foo() {})", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] },
+        { code: "foo.every(() => {})", parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Array.prototype.every() expects a return value from arrow function.", column: 14 }] },
+        { code: "foo.every(function() { if (a) return true; })", errors: [{ message: "Array.prototype.every() expects a value to be returned at the end of function.", column: 11 }] },
+        { code: "foo.every(function cb() { if (a) return true; })", errors: [{ message: "Array.prototype.every() expects a value to be returned at the end of function 'cb'.", column: 11 }] },
+        { code: "foo.every(function() { switch (a) { case 0: break; default: return true; } })", errors: [{ messageId: "expectedAtEnd", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] },
+        { code: "foo.every(function foo() { switch (a) { case 0: break; default: return true; } })", errors: [{ messageId: "expectedAtEnd", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] },
+        { code: "foo.every(function() { try { bar(); } catch (err) { return true; } })", errors: [{ messageId: "expectedAtEnd", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] },
+        { code: "foo.every(function foo() { try { bar(); } catch (err) { return true; } })", errors: [{ messageId: "expectedAtEnd", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] },
+        { code: "foo.every(function() { return; })", errors: [{ messageId: "expectedReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] },
+        { code: "foo.every(function foo() { return; })", errors: [{ messageId: "expectedReturnValue", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] },
+        { code: "foo.every(function() { if (a) return; })", errors: ["Array.prototype.every() expects a value to be returned at the end of function.", { messageId: "expectedReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] },
+        { code: "foo.every(function foo() { if (a) return; })", errors: ["Array.prototype.every() expects a value to be returned at the end of function 'foo'.", { messageId: "expectedReturnValue", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] },
+        { code: "foo.every(function() { if (a) return; else return; })", errors: [{ messageId: "expectedReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.every" } }, { messageId: "expectedReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] },
+        { code: "foo.every(function foo() { if (a) return; else return; })", errors: [{ messageId: "expectedReturnValue", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }, { messageId: "expectedReturnValue", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] },
+        { code: "foo.every(cb || function() {})", errors: ["Array.prototype.every() expects a return value from function."] },
+        { code: "foo.every(cb || function foo() {})", errors: ["Array.prototype.every() expects a return value from function 'foo'."] },
+        { code: "foo.every(a ? function() {} : function() {})", errors: ["Array.prototype.every() expects a return value from function.", "Array.prototype.every() expects a return value from function."] },
+        { code: "foo.every(a ? function foo() {} : function bar() {})", errors: ["Array.prototype.every() expects a return value from function 'foo'.", "Array.prototype.every() expects a return value from function 'bar'."] },
+        { code: "foo.every(function(){ return function() {}; }())", errors: [{ message: "Array.prototype.every() expects a return value from function.", column: 30 }] },
+        { code: "foo.every(function(){ return function foo() {}; }())", errors: [{ message: "Array.prototype.every() expects a return value from function 'foo'.", column: 30 }] },
+        { code: "foo.every(() => {})", options: [{ allowImplicit: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Array.prototype.every() expects a return value from arrow function." }] },
+        { code: "foo.every(() => {})", options: [{ allowImplicit: true }], parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Array.prototype.every() expects a return value from arrow function." }] },
 
         // options: { allowImplicit: true }
-        { code: "Array.from(x, function() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
-        { code: "foo.every(function() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
-        { code: "foo.filter(function foo() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
-        { code: "foo.find(function foo() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
-        { code: "foo.map(function() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
-        { code: "foo.reduce(function() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
-        { code: "foo.reduceRight(function() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
-        { code: "foo.bar.baz.every(function foo() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
-        { code: "foo.every(cb || function() {})", options: allowImplicitOptions, errors: ["Expected to return a value in function."] },
-        { code: "[\"foo\",\"bar\"].sort(function foo() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
-        { code: "foo.forEach(x => x)", options: allowImplicitCheckForEach, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Arrow function" } }] },
-        { code: "foo.forEach(function(x) { if (a == b) {return x;}})", options: allowImplicitCheckForEach, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Function" } }] },
-        { code: "foo.forEach(function bar(x) { return x;})", options: allowImplicitCheckForEach, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Function 'bar'" } }] },
+        { code: "Array.from(x, function() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.from" } }] },
+        { code: "foo.every(function() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] },
+        { code: "foo.filter(function foo() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.filter" } }] },
+        { code: "foo.find(function foo() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.find" } }] },
+        { code: "foo.map(function() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.map" } }] },
+        { code: "foo.reduce(function() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.reduce" } }] },
+        { code: "foo.reduceRight(function() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.reduceRight" } }] },
+        { code: "foo.bar.baz.every(function foo() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] },
+        { code: "foo.every(cb || function() {})", options: allowImplicitOptions, errors: ["Array.prototype.every() expects a return value from function."] },
+        { code: "[\"foo\",\"bar\"].sort(function foo() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.sort" } }] },
+        { code: "foo.forEach(x => x)", options: allowImplicitCheckForEach, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] },
+        { code: "foo.forEach(function(x) { if (a == b) {return x;}})", options: allowImplicitCheckForEach, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.forEach" } }] },
+        { code: "foo.forEach(function bar(x) { return x;})", options: allowImplicitCheckForEach, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function 'bar'", arrayMethodName: "Array.prototype.forEach" } }] },
 
         // // options: { checkForEach: true }
-        { code: "foo.forEach(x => x)", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Arrow function" } }] },
-        { code: "foo.forEach(val => y += val)", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Arrow function" } }] },
-        { code: "[\"foo\",\"bar\"].forEach(x => ++x)", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Arrow function" } }] },
-        { code: "foo.bar().forEach(x => x === y)", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Arrow function" } }] },
-        { code: "foo.forEach(function() {return function() { if (a == b) { return a; }}}())", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Function" } }] },
-        { code: "foo.forEach(function(x) { if (a == b) {return x;}})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Function" } }] },
-        { code: "foo.forEach(function(x) { if (a == b) {return undefined;}})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Function" } }] },
-        { code: "foo.forEach(function bar(x) { return x;})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Function 'bar'" } }] },
-        { code: "foo.bar().forEach(function bar(x) { return x;})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Function 'bar'" } }] },
-        { code: "[\"foo\",\"bar\"].forEach(function bar(x) { return x;})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Function 'bar'" } }] },
-        { code: "foo.forEach((x) => { return x;})", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "Arrow function" } }] },
-        { code: "Array.from(x, function() {})", options: checkForEachOptions, errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
-        { code: "foo.every(function() {})", options: checkForEachOptions, errors: [{ messageId: "expectedInside", data: { name: "function" } }] },
-        { code: "foo.filter(function foo() {})", options: checkForEachOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'" } }] },
-        { code: "foo.filter(function foo() { return; })", options: checkForEachOptions, errors: [{ messageId: "expectedReturnValue", data: { name: "Function 'foo'" } }] },
-        { code: "foo.every(cb || function() {})", options: checkForEachOptions, errors: ["Expected to return a value in function."] },
+        { code: "foo.forEach(x => x)", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] },
+        { code: "foo.forEach(val => y += val)", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] },
+        { code: "[\"foo\",\"bar\"].forEach(x => ++x)", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] },
+        { code: "foo.bar().forEach(x => x === y)", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] },
+        { code: "foo.forEach(function() {return function() { if (a == b) { return a; }}}())", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.forEach" } }] },
+        { code: "foo.forEach(function(x) { if (a == b) {return x;}})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.forEach" } }] },
+        { code: "foo.forEach(function(x) { if (a == b) {return undefined;}})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.forEach" } }] },
+        { code: "foo.forEach(function bar(x) { return x;})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function 'bar'", arrayMethodName: "Array.prototype.forEach" } }] },
+        { code: "foo.forEach(function bar(x) { return x;})", options: checkForEachOptions, errors: ["Array.prototype.forEach() expects no useless return value from function 'bar'."] },
+        { code: "foo.bar().forEach(function bar(x) { return x;})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function 'bar'", arrayMethodName: "Array.prototype.forEach" } }] },
+        { code: "[\"foo\",\"bar\"].forEach(function bar(x) { return x;})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function 'bar'", arrayMethodName: "Array.prototype.forEach" } }] },
+        { code: "foo.forEach((x) => { return x;})", options: checkForEachOptions, parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] },
+        { code: "Array.from(x, function() {})", options: checkForEachOptions, errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.from" } }] },
+        { code: "foo.every(function() {})", options: checkForEachOptions, errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] },
+        { code: "foo.filter(function foo() {})", options: checkForEachOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.filter" } }] },
+        { code: "foo.filter(function foo() { return; })", options: checkForEachOptions, errors: [{ messageId: "expectedReturnValue", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.filter" } }] },
+        { code: "foo.every(cb || function() {})", options: checkForEachOptions, errors: ["Array.prototype.every() expects a return value from function."] },
 
         // full location tests
         {
@@ -206,7 +207,7 @@ ruleTester.run("array-callback-return", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "expectedInside",
-                data: { name: "arrow function" },
+                data: { name: "arrow function", arrayMethodName: "Array.prototype.filter" },
                 type: "ArrowFunctionExpression",
                 line: 1,
                 column: 16,
@@ -219,7 +220,7 @@ ruleTester.run("array-callback-return", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "expectedInside",
-                data: { name: "arrow function" },
+                data: { name: "arrow function", arrayMethodName: "Array.prototype.filter" },
                 type: "ArrowFunctionExpression",
                 line: 2,
                 column: 4,
@@ -232,7 +233,7 @@ ruleTester.run("array-callback-return", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "expectedInside",
-                data: { name: "arrow function" },
+                data: { name: "arrow function", arrayMethodName: "Array.prototype.filter" },
                 type: "ArrowFunctionExpression",
                 line: 1,
                 column: 26,
@@ -245,7 +246,7 @@ ruleTester.run("array-callback-return", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "expectedReturnValue",
-                data: { name: "Arrow function" },
+                data: { name: "arrow function", arrayMethodName: "Array.prototype.filter" },
                 type: "ReturnStatement",
                 line: 1,
                 column: 21,
@@ -258,7 +259,7 @@ ruleTester.run("array-callback-return", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "expectedInside",
-                data: { name: "arrow function" },
+                data: { name: "arrow function", arrayMethodName: "Array.from" },
                 type: "ArrowFunctionExpression",
                 line: 1,
                 column: 21,
@@ -272,7 +273,7 @@ ruleTester.run("array-callback-return", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "expectedNoReturnValue",
-                data: { name: "Arrow function" },
+                data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" },
                 type: "ArrowFunctionExpression",
                 line: 1,
                 column: 17,
@@ -286,7 +287,7 @@ ruleTester.run("array-callback-return", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "expectedNoReturnValue",
-                data: { name: "Arrow function" },
+                data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" },
                 type: "ArrowFunctionExpression",
                 line: 1,
                 column: 41,
@@ -300,7 +301,7 @@ ruleTester.run("array-callback-return", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "expectedNoReturnValue",
-                data: { name: "Arrow function" },
+                data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" },
                 type: "ArrowFunctionExpression",
                 line: 2,
                 column: 13,
@@ -314,7 +315,7 @@ ruleTester.run("array-callback-return", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 messageId: "expectedNoReturnValue",
-                data: { name: "Arrow function" },
+                data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" },
                 type: "ReturnStatement",
                 line: 1,
                 column: 52,
@@ -326,7 +327,7 @@ ruleTester.run("array-callback-return", rule, {
             code: "foo.filter(function(){})",
             errors: [{
                 messageId: "expectedInside",
-                data: { name: "function" },
+                data: { name: "function", arrayMethodName: "Array.prototype.filter" },
                 type: "FunctionExpression",
                 line: 1,
                 column: 12,
@@ -338,7 +339,7 @@ ruleTester.run("array-callback-return", rule, {
             code: "foo.filter(function (){})",
             errors: [{
                 messageId: "expectedInside",
-                data: { name: "function" },
+                data: { name: "function", arrayMethodName: "Array.prototype.filter" },
                 type: "FunctionExpression",
                 line: 1,
                 column: 12,
@@ -350,7 +351,7 @@ ruleTester.run("array-callback-return", rule, {
             code: "foo.filter(function\n(){})",
             errors: [{
                 messageId: "expectedInside",
-                data: { name: "function" },
+                data: { name: "function", arrayMethodName: "Array.prototype.filter" },
                 type: "FunctionExpression",
                 line: 1,
                 column: 12,
@@ -362,7 +363,7 @@ ruleTester.run("array-callback-return", rule, {
             code: "foo.filter(function bar(){})",
             errors: [{
                 messageId: "expectedInside",
-                data: { name: "function 'bar'" },
+                data: { name: "function 'bar'", arrayMethodName: "Array.prototype.filter" },
                 type: "FunctionExpression",
                 line: 1,
                 column: 12,
@@ -374,7 +375,7 @@ ruleTester.run("array-callback-return", rule, {
             code: "foo.filter(function bar  (){})",
             errors: [{
                 messageId: "expectedInside",
-                data: { name: "function 'bar'" },
+                data: { name: "function 'bar'", arrayMethodName: "Array.prototype.filter" },
                 type: "FunctionExpression",
                 line: 1,
                 column: 12,
@@ -386,7 +387,7 @@ ruleTester.run("array-callback-return", rule, {
             code: "foo.filter(function\n bar() {})",
             errors: [{
                 messageId: "expectedInside",
-                data: { name: "function 'bar'" },
+                data: { name: "function 'bar'", arrayMethodName: "Array.prototype.filter" },
                 type: "FunctionExpression",
                 line: 1,
                 column: 12,
@@ -398,7 +399,7 @@ ruleTester.run("array-callback-return", rule, {
             code: "Array.from(foo, function bar(){})",
             errors: [{
                 messageId: "expectedInside",
-                data: { name: "function 'bar'" },
+                data: { name: "function 'bar'", arrayMethodName: "Array.from" },
                 type: "FunctionExpression",
                 line: 1,
                 column: 17,
@@ -410,7 +411,7 @@ ruleTester.run("array-callback-return", rule, {
             code: "Array.from(foo, bar ? function (){} : baz)",
             errors: [{
                 messageId: "expectedInside",
-                data: { name: "function" },
+                data: { name: "function", arrayMethodName: "Array.from" },
                 type: "FunctionExpression",
                 line: 1,
                 column: 23,
@@ -422,7 +423,7 @@ ruleTester.run("array-callback-return", rule, {
             code: "foo.filter(function bar() { return \n })",
             errors: [{
                 messageId: "expectedReturnValue",
-                data: { name: "Function 'bar'" },
+                data: { name: "function 'bar'", arrayMethodName: "Array.prototype.filter" },
                 type: "ReturnStatement",
                 line: 1,
                 column: 29,
@@ -435,13 +436,40 @@ ruleTester.run("array-callback-return", rule, {
             options: checkForEachOptions,
             errors: [{
                 messageId: "expectedNoReturnValue",
-                data: { name: "Function" },
+                data: { name: "function", arrayMethodName: "Array.prototype.forEach" },
                 type: "ReturnStatement",
                 line: 2,
                 column: 10,
                 endLine: 2,
                 endColumn: 20
             }]
+        },
+
+        // Optional chaining
+        {
+            code: "foo?.filter(() => { console.log('hello') })",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "expectedInside", data: { name: "arrow function", arrayMethodName: "Array.prototype.filter" } }]
+        },
+        {
+            code: "(foo?.filter)(() => { console.log('hello') })",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "expectedInside", data: { name: "arrow function", arrayMethodName: "Array.prototype.filter" } }]
+        },
+        {
+            code: "Array?.from([], () => { console.log('hello') })",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "expectedInside", data: { name: "arrow function", arrayMethodName: "Array.from" } }]
+        },
+        {
+            code: "(Array?.from)([], () => { console.log('hello') })",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "expectedInside", data: { name: "arrow function", arrayMethodName: "Array.from" } }]
+        },
+        {
+            code: "foo?.filter((function() { return () => { console.log('hello') } })?.())",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "expectedInside", data: { name: "arrow function", arrayMethodName: "Array.prototype.filter" } }]
         }
     ]
 });
index 7c1bea64ef8c942fd5ac44f7a123bd911aea9a03..7a8de4fe5eff6ff36c019d1e2ea52f0d346905bd 100644 (file)
@@ -45,6 +45,239 @@ ruleTester.run("arrow-body-style", rule, {
         { code: "var foo = () => { return { bar: 0 }; };", options: ["as-needed", { requireReturnForObjectLiteral: true }] }
     ],
     invalid: [
+        {
+            code: "for (var foo = () => { return a in b ? bar : () => {} } ;;);",
+            output: "for (var foo = () => (a in b ? bar : () => {}) ;;);",
+            options: ["as-needed"],
+            errors: [
+                {
+                    line: 1,
+                    column: 22,
+                    messageId: "unexpectedSingleBlock"
+                }
+            ]
+        },
+        {
+            code: "a in b; for (var f = () => { return c };;);",
+            output: "a in b; for (var f = () => c;;);",
+            options: ["as-needed"],
+            errors: [
+                {
+                    line: 1,
+                    column: 28,
+                    messageId: "unexpectedSingleBlock"
+                }
+            ]
+        },
+        {
+            code: "for (a = b => { return c in d ? e : f } ;;);",
+            output: "for (a = b => (c in d ? e : f) ;;);",
+            options: ["as-needed"],
+            errors: [
+                {
+                    line: 1,
+                    column: 15,
+                    messageId: "unexpectedSingleBlock"
+                }
+            ]
+        },
+        {
+            code: "for (var f = () => { return a };;);",
+            output: "for (var f = () => a;;);",
+            options: ["as-needed"],
+            errors: [
+                {
+                    line: 1,
+                    column: 20,
+                    messageId: "unexpectedSingleBlock"
+                }
+            ]
+        },
+        {
+            code: "for (var f;f = () => { return a };);",
+            output: "for (var f;f = () => a;);",
+            options: ["as-needed"],
+            errors: [
+                {
+                    line: 1,
+                    column: 22,
+                    messageId: "unexpectedSingleBlock"
+                }
+            ]
+        },
+        {
+            code: "for (var f = () => { return a in c };;);",
+            output: "for (var f = () => (a in c);;);",
+            options: ["as-needed"],
+            errors: [
+                {
+                    line: 1,
+                    column: 20,
+                    messageId: "unexpectedSingleBlock"
+                }
+            ]
+        },
+        {
+            code: "for (var f;f = () => { return a in c };);",
+            output: "for (var f;f = () => a in c;);",
+            options: ["as-needed"],
+            errors: [
+                {
+                    line: 1,
+                    column: 22,
+                    messageId: "unexpectedSingleBlock"
+                }
+            ]
+        },
+        {
+            code: "for (;;){var f = () => { return a in c }}",
+            output: "for (;;){var f = () => a in c}",
+            options: ["as-needed"],
+            errors: [
+                {
+                    line: 1,
+                    column: 24,
+                    messageId: "unexpectedSingleBlock"
+                }
+            ]
+        },
+        {
+            code: "for (a = b => { return c = d in e } ;;);",
+            output: "for (a = b => (c = d in e) ;;);",
+            options: ["as-needed"],
+            errors: [
+                {
+                    line: 1,
+                    column: 15,
+                    messageId: "unexpectedSingleBlock"
+                }
+            ]
+        },
+        {
+            code: "for (var a;;a = b => { return c = d in e } );",
+            output: "for (var a;;a = b => c = d in e );",
+            options: ["as-needed"],
+            errors: [
+                {
+                    line: 1,
+                    column: 22,
+                    messageId: "unexpectedSingleBlock"
+                }
+            ]
+        },
+        {
+            code: "for (let a = (b, c, d) => { return vb && c in d; }; ;);",
+            output: "for (let a = (b, c, d) => (vb && c in d); ;);",
+            errors: [
+                {
+                    line: 1,
+                    column: 27,
+                    messageId: "unexpectedSingleBlock"
+                }
+            ]
+        },
+        {
+            code: "for (let a = (b, c, d) => { return v in b && c in d; }; ;);",
+            output: "for (let a = (b, c, d) => (v in b && c in d); ;);",
+            errors: [
+                {
+                    line: 1,
+                    column: 27,
+                    messageId: "unexpectedSingleBlock"
+                }
+            ]
+        },
+        {
+            code: "function foo(){ for (let a = (b, c, d) => { return v in b && c in d; }; ;); }",
+            output: "function foo(){ for (let a = (b, c, d) => (v in b && c in d); ;); }",
+            errors: [
+                {
+                    line: 1,
+                    column: 43,
+                    messageId: "unexpectedSingleBlock"
+                }
+            ]
+        },
+        {
+            code: "for ( a = (b, c, d) => { return v in b && c in d; }; ;);",
+            output: "for ( a = (b, c, d) => (v in b && c in d); ;);",
+            errors: [
+                {
+                    line: 1,
+                    column: 24,
+                    messageId: "unexpectedSingleBlock"
+                }
+            ]
+        },
+        {
+            code: "for ( a = (b) => { return (c in d) }; ;);",
+            output: "for ( a = (b) => (c in d); ;);",
+            errors: [
+                {
+                    line: 1,
+                    column: 18,
+                    messageId: "unexpectedSingleBlock"
+                }
+            ]
+        },
+        {
+            code: "for (let a = (b, c, d) => { return vb in dd ; }; ;);",
+            output: "for (let a = (b, c, d) => (vb in dd ); ;);",
+            errors: [
+                {
+                    line: 1,
+                    column: 27,
+                    messageId: "unexpectedSingleBlock"
+                }
+            ]
+        },
+        {
+            code: "for (let a = (b, c, d) => { return vb in c in dd ; }; ;);",
+            output: "for (let a = (b, c, d) => (vb in c in dd ); ;);",
+            errors: [
+                {
+                    line: 1,
+                    column: 27,
+                    messageId: "unexpectedSingleBlock"
+                }
+            ]
+        },
+        {
+            code: "do{let a = () => {return f in ff}}while(true){}",
+            output: "do{let a = () => f in ff}while(true){}",
+            errors: [{
+                line: 1,
+                column: 18,
+                messageId: "unexpectedSingleBlock"
+            }]
+        },
+        {
+            code: "do{for (let a = (b, c, d) => { return vb in c in dd ; }; ;);}while(true){}",
+            output: "do{for (let a = (b, c, d) => (vb in c in dd ); ;);}while(true){}",
+            errors: [{
+                line: 1,
+                column: 30,
+                messageId: "unexpectedSingleBlock"
+            }]
+        },
+        {
+            code: "scores.map(score => { return x in +(score / maxScore).toFixed(2)});",
+            output: "scores.map(score => x in +(score / maxScore).toFixed(2));",
+            errors: [{
+                line: 1,
+                column: 21,
+                messageId: "unexpectedSingleBlock"
+            }]
+        },
+        {
+            code: "const fn = (a, b) => { return a + x in Number(b) };",
+            output: "const fn = (a, b) => a + x in Number(b);",
+            errors: [{
+                line: 1,
+                column: 22,
+                messageId: "unexpectedSingleBlock"
+            }]
+        },
         {
             code: "var foo = () => 0",
             output: "var foo = () => {return 0}",
@@ -53,6 +286,8 @@ ruleTester.run("arrow-body-style", rule, {
                 {
                     line: 1,
                     column: 17,
+                    endLine: 1,
+                    endColumn: 18,
                     type: "ArrowFunctionExpression",
                     messageId: "expectedBlock"
                 }
@@ -368,8 +603,8 @@ ruleTester.run("arrow-body-style", rule, {
 
             // Not fixed; fixing would cause ASI issues.
             code:
-            "var foo = () => { return bar }\n" +
-            "[1, 2, 3].map(foo)",
+        "var foo = () => { return bar }\n" +
+        "[1, 2, 3].map(foo)",
             output: null,
             options: ["never"],
             errors: [
@@ -378,10 +613,11 @@ ruleTester.run("arrow-body-style", rule, {
         },
         {
 
+
             // Not fixed; fixing would cause ASI issues.
             code:
-            "var foo = () => { return bar }\n" +
-            "(1).toString();",
+        "var foo = () => { return bar }\n" +
+        "(1).toString();",
             output: null,
             options: ["never"],
             errors: [
@@ -440,6 +676,8 @@ ruleTester.run("arrow-body-style", rule, {
                 {
                     line: 1,
                     column: 17,
+                    endLine: 3,
+                    endColumn: 2,
                     type: "ArrowFunctionExpression",
                     messageId: "unexpectedSingleBlock"
                 }
@@ -452,6 +690,8 @@ ruleTester.run("arrow-body-style", rule, {
                 {
                     line: 1,
                     column: 17,
+                    endLine: 2,
+                    endColumn: 13,
                     type: "ArrowFunctionExpression",
                     messageId: "unexpectedSingleBlock"
                 }
@@ -464,6 +704,8 @@ ruleTester.run("arrow-body-style", rule, {
                 {
                     line: 1,
                     column: 17,
+                    endLine: 2,
+                    endColumn: 2,
                     type: "ArrowFunctionExpression",
                     messageId: "unexpectedSingleBlock"
                 }
@@ -508,6 +750,8 @@ ruleTester.run("arrow-body-style", rule, {
                 {
                     line: 2,
                     column: 31,
+                    endLine: 7,
+                    endColumn: 16,
                     type: "ArrowFunctionExpression",
                     messageId: "unexpectedObjectBlock"
                 }
index edd1ae6da8a3e3e10bd5cdc98017b3ca93093a4f..61d7635844085e5174d4b1ccff4d8aa1f06b986f 100644 (file)
@@ -45,16 +45,22 @@ const valid = [
     { code: "a.then((foo) => {});", options: ["always"] },
     { code: "a.then((foo) => { if (true) {}; });", options: ["always"] },
     { code: "a.then(async (foo) => { if (true) {}; });", options: ["always"], parserOptions: { ecmaVersion: 8 } },
+    { code: "(a: T) => a", options: ["always"], parser: parser("identifer-type") },
+    { code: "(a): T => a", options: ["always"], parser: parser("return-type") },
 
     // "as-needed"
     { code: "() => {}", options: ["as-needed"] },
     { code: "a => {}", options: ["as-needed"] },
     { code: "a => a", options: ["as-needed"] },
+    { code: "a => (a)", options: ["as-needed"] },
+    { code: "(a => a)", options: ["as-needed"] },
+    { code: "((a => a))", options: ["as-needed"] },
     { code: "([a, b]) => {}", options: ["as-needed"] },
     { code: "({ a, b }) => {}", options: ["as-needed"] },
     { code: "(a = 10) => {}", options: ["as-needed"] },
     { code: "(...a) => a[0]", options: ["as-needed"] },
     { code: "(a, b) => {}", options: ["as-needed"] },
+    { code: "async a => a", options: ["as-needed"], parserOptions: { ecmaVersion: 8 } },
     { code: "async ([a, b]) => {}", options: ["as-needed"], parserOptions: { ecmaVersion: 8 } },
     { code: "async (a, b) => {}", options: ["as-needed"], parserOptions: { ecmaVersion: 8 } },
     { code: "(a: T) => a", options: ["as-needed"], parser: parser("identifer-type") },
@@ -63,6 +69,9 @@ const valid = [
     // "as-needed", { "requireForBlockBody": true }
     { code: "() => {}", options: ["as-needed", { requireForBlockBody: true }] },
     { code: "a => a", options: ["as-needed", { requireForBlockBody: true }] },
+    { code: "a => (a)", options: ["as-needed", { requireForBlockBody: true }] },
+    { code: "(a => a)", options: ["as-needed", { requireForBlockBody: true }] },
+    { code: "((a => a))", options: ["as-needed", { requireForBlockBody: true }] },
     { code: "([a, b]) => {}", options: ["as-needed", { requireForBlockBody: true }] },
     { code: "([a, b]) => a", options: ["as-needed", { requireForBlockBody: true }] },
     { code: "({ a, b }) => {}", options: ["as-needed", { requireForBlockBody: true }] },
@@ -136,6 +145,83 @@ const valid = [
     {
         code: "var bar = (/*comment here*/{a}) => a",
         options: ["as-needed"]
+    },
+
+    // generics
+    {
+        code: "<T>(a) => b",
+        options: ["always"],
+        parser: parser("generics-simple")
+    },
+    {
+        code: "<T>(a) => b",
+        options: ["as-needed"],
+        parser: parser("generics-simple")
+    },
+    {
+        code: "<T>(a) => b",
+        options: ["as-needed", { requireForBlockBody: true }],
+        parser: parser("generics-simple")
+    },
+    {
+        code: "async <T>(a) => b",
+        options: ["always"],
+        parser: parser("generics-simple-async")
+    },
+    {
+        code: "async <T>(a) => b",
+        options: ["as-needed"],
+        parser: parser("generics-simple-async")
+    },
+    {
+        code: "async <T>(a) => b",
+        options: ["as-needed", { requireForBlockBody: true }],
+        parser: parser("generics-simple-async")
+    },
+    {
+        code: "<T>() => b",
+        options: ["always"],
+        parser: parser("generics-simple-no-params")
+    },
+    {
+        code: "<T>() => b",
+        options: ["as-needed"],
+        parser: parser("generics-simple-no-params")
+    },
+    {
+        code: "<T>() => b",
+        options: ["as-needed", { requireForBlockBody: true }],
+        parser: parser("generics-simple-no-params")
+    },
+    {
+        code: "<T extends A>(a) => b",
+        options: ["always"],
+        parser: parser("generics-extends")
+    },
+    {
+        code: "<T extends A>(a) => b",
+        options: ["as-needed"],
+        parser: parser("generics-extends")
+    },
+    {
+        code: "<T extends A>(a) => b",
+        options: ["as-needed", { requireForBlockBody: true }],
+        parser: parser("generics-extends")
+    },
+    {
+        code: "<T extends (A | B) & C>(a) => b",
+        options: ["always"],
+        parser: parser("generics-extends-complex")
+    },
+    {
+        code: "<T extends (A | B) & C>(a) => b",
+        options: ["as-needed"],
+        parser: parser("generics-extends-complex")
+    },
+    {
+        code: "<T extends (A | B) & C>(a) => b",
+        options: ["as-needed", { requireForBlockBody: true }],
+        parser: parser("generics-extends-complex")
     }
 ];
 
@@ -236,6 +322,30 @@ const invalid = [
             type
         }]
     },
+    {
+        code: "(  a  ) => b",
+        output: "a => b",
+        options: ["as-needed"],
+        errors: [{
+            line: 1,
+            column: 4,
+            endColumn: 5,
+            messageId: "unexpectedParens",
+            type
+        }]
+    },
+    {
+        code: "(\na\n) => b",
+        output: "a => b",
+        options: ["as-needed"],
+        errors: [{
+            line: 2,
+            column: 1,
+            endColumn: 2,
+            messageId: "unexpectedParens",
+            type
+        }]
+    },
     {
         code: "(a,) => a",
         output: "a => a",
@@ -275,6 +385,30 @@ const invalid = [
             type
         }]
     },
+    {
+        code: "typeof((a) => {})",
+        output: "typeof(a => {})",
+        options: ["as-needed"],
+        errors: [{
+            line: 1,
+            column: 9,
+            endColumn: 10,
+            messageId: "unexpectedParens",
+            type
+        }]
+    },
+    {
+        code: "function *f() { yield(a) => a; }",
+        output: "function *f() { yield a => a; }",
+        options: ["as-needed"],
+        errors: [{
+            line: 1,
+            column: 23,
+            endColumn: 24,
+            messageId: "unexpectedParens",
+            type
+        }]
+    },
 
     // "as-needed", { "requireForBlockBody": true }
     {
index 7c7a4576c77d13bad4541744857ae6d51df7410e..4f9cdca78fe44399022362c923a8cce78167f46c 100644 (file)
@@ -169,6 +169,104 @@ ruleTester.run("camelcase", rule, {
             options: [{ ignoreImports: false }],
             parserOptions: { ecmaVersion: 6, sourceType: "module" }
         },
+        {
+            code: "var _camelCased = aGlobalVariable",
+            options: [{ ignoreGlobals: false }],
+            globals: { aGlobalVariable: "readonly" }
+        },
+        {
+            code: "var camelCased = _aGlobalVariable",
+            options: [{ ignoreGlobals: false }],
+            globals: { _aGlobalVariable: "readonly" }
+        },
+        {
+            code: "var camelCased = a_global_variable",
+            options: [{ ignoreGlobals: true }],
+            globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+        },
+        {
+            code: "a_global_variable.foo()",
+            options: [{ ignoreGlobals: true }],
+            globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+        },
+        {
+            code: "a_global_variable[undefined]",
+            options: [{ ignoreGlobals: true }],
+            globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+        },
+        {
+            code: "var foo = a_global_variable.bar",
+            options: [{ ignoreGlobals: true }],
+            globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+        },
+        {
+            code: "a_global_variable.foo = bar",
+            options: [{ ignoreGlobals: true }],
+            globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+        },
+        {
+            code: "( { foo: a_global_variable.bar } = baz )",
+            options: [{ ignoreGlobals: true }],
+            parserOptions: { ecmaVersion: 6 },
+            globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+        },
+        {
+            code: "a_global_variable = foo",
+            options: [{ ignoreGlobals: true }],
+            globals: { a_global_variable: "writable" } // eslint-disable-line camelcase
+        },
+        {
+            code: "a_global_variable = foo",
+            options: [{ ignoreGlobals: true }],
+            globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+        },
+        {
+            code: "({ a_global_variable } = foo)",
+            options: [{ ignoreGlobals: true }],
+            parserOptions: { ecmaVersion: 6 },
+            globals: { a_global_variable: "writable" } // eslint-disable-line camelcase
+        },
+        {
+            code: "({ snake_cased: a_global_variable } = foo)",
+            options: [{ ignoreGlobals: true }],
+            parserOptions: { ecmaVersion: 6 },
+            globals: { a_global_variable: "writable" } // eslint-disable-line camelcase
+        },
+        {
+            code: "({ snake_cased: a_global_variable = foo } = bar)",
+            options: [{ ignoreGlobals: true }],
+            parserOptions: { ecmaVersion: 6 },
+            globals: { a_global_variable: "writable" } // eslint-disable-line camelcase
+        },
+        {
+            code: "[a_global_variable] = bar",
+            options: [{ ignoreGlobals: true }],
+            parserOptions: { ecmaVersion: 6 },
+            globals: { a_global_variable: "writable" } // eslint-disable-line camelcase
+        },
+        {
+            code: "[a_global_variable = foo] = bar",
+            options: [{ ignoreGlobals: true }],
+            parserOptions: { ecmaVersion: 6 },
+            globals: { a_global_variable: "writable" } // eslint-disable-line camelcase
+        },
+        {
+            code: "foo[a_global_variable] = bar",
+            options: [{ ignoreGlobals: true }],
+            globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+        },
+        {
+            code: "var foo = { [a_global_variable]: bar }",
+            options: [{ ignoreGlobals: true }],
+            parserOptions: { ecmaVersion: 6 },
+            globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+        },
+        {
+            code: "var { [a_global_variable]: foo } = bar",
+            options: [{ ignoreGlobals: true }],
+            parserOptions: { ecmaVersion: 6 },
+            globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase
+        },
         {
             code: "function foo({ no_camelcased: camelCased }) {};",
             parserOptions: { ecmaVersion: 6 }
@@ -652,6 +750,257 @@ ruleTester.run("camelcase", rule, {
                 }
             ]
         },
+        {
+            code: "var camelCased = snake_cased",
+            options: [{ ignoreGlobals: false }],
+            globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase
+            errors: [
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "snake_cased" },
+                    type: "Identifier"
+                }
+            ]
+        },
+        {
+            code: "a_global_variable.foo()",
+            options: [{ ignoreGlobals: false }],
+            globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase
+            errors: [
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "a_global_variable" },
+                    type: "Identifier"
+                }
+            ]
+        },
+        {
+            code: "a_global_variable[undefined]",
+            options: [{ ignoreGlobals: false }],
+            globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase
+            errors: [
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "a_global_variable" },
+                    type: "Identifier"
+                }
+            ]
+        },
+        {
+            code: "var camelCased = snake_cased",
+            globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase
+            errors: [
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "snake_cased" },
+                    type: "Identifier"
+                }
+            ]
+        },
+        {
+            code: "var camelCased = snake_cased",
+            options: [{}],
+            globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase
+            errors: [
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "snake_cased" },
+                    type: "Identifier"
+                }
+            ]
+        },
+        {
+            code: "foo.a_global_variable = bar",
+            options: [{ ignoreGlobals: true }],
+            globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+            errors: [
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "a_global_variable" },
+                    type: "Identifier"
+                }
+            ]
+        },
+        {
+            code: "var foo = { a_global_variable: bar }",
+            options: [{ ignoreGlobals: true }],
+            globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+            errors: [
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "a_global_variable" },
+                    type: "Identifier"
+                }
+            ]
+        },
+        {
+            code: "var foo = { a_global_variable: a_global_variable }",
+            options: [{ ignoreGlobals: true }],
+            globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+            errors: [
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "a_global_variable" },
+                    type: "Identifier",
+                    column: 13
+                }
+            ]
+        },
+        {
+            code: "var foo = { a_global_variable() {} }",
+            options: [{ ignoreGlobals: true }],
+            parserOptions: { ecmaVersion: 6 },
+            globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+            errors: [
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "a_global_variable" },
+                    type: "Identifier"
+                }
+            ]
+        },
+        {
+            code: "class Foo { a_global_variable() {} }",
+            options: [{ ignoreGlobals: true }],
+            parserOptions: { ecmaVersion: 6 },
+            globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+            errors: [
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "a_global_variable" },
+                    type: "Identifier"
+                }
+            ]
+        },
+        {
+            code: "a_global_variable: for (;;);",
+            options: [{ ignoreGlobals: true }],
+            globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+            errors: [
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "a_global_variable" },
+                    type: "Identifier"
+                }
+            ]
+        },
+        {
+            code: "if (foo) { let a_global_variable; a_global_variable = bar; }",
+            options: [{ ignoreGlobals: true }],
+            parserOptions: { ecmaVersion: 6 },
+            globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+            errors: [
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "a_global_variable" },
+                    type: "Identifier",
+                    column: 16
+                },
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "a_global_variable" },
+                    type: "Identifier",
+                    column: 35
+                }
+            ]
+        },
+        {
+            code: "function foo(a_global_variable) { foo = a_global_variable; }",
+            options: [{ ignoreGlobals: true }],
+            parserOptions: { ecmaVersion: 6 },
+            globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+            errors: [
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "a_global_variable" },
+                    type: "Identifier",
+                    column: 14
+                },
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "a_global_variable" },
+                    type: "Identifier",
+                    column: 41
+                }
+            ]
+        },
+        {
+            code: "var a_global_variable",
+            options: [{ ignoreGlobals: true }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "a_global_variable" },
+                    type: "Identifier"
+                }
+            ]
+        },
+        {
+            code: "function a_global_variable () {}",
+            options: [{ ignoreGlobals: true }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "a_global_variable" },
+                    type: "Identifier"
+                }
+            ]
+        },
+        {
+            code: "const a_global_variable = foo; bar = a_global_variable",
+            options: [{ ignoreGlobals: true }],
+            parserOptions: { ecmaVersion: 6 },
+            globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+            errors: [
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "a_global_variable" },
+                    type: "Identifier",
+                    column: 7
+                },
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "a_global_variable" },
+                    type: "Identifier",
+                    column: 38
+                }
+            ]
+        },
+        {
+            code: "bar = a_global_variable; var a_global_variable;",
+            options: [{ ignoreGlobals: true }],
+            parserOptions: { ecmaVersion: 6 },
+            globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase
+            errors: [
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "a_global_variable" },
+                    type: "Identifier",
+                    column: 7
+                },
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "a_global_variable" },
+                    type: "Identifier",
+                    column: 30
+                }
+            ]
+        },
+        {
+            code: "var foo = { a_global_variable }",
+            options: [{ ignoreGlobals: true }],
+            parserOptions: { ecmaVersion: 6 },
+            globals: { a_global_variable: "readonly" }, // eslint-disable-line camelcase
+            errors: [
+                {
+                    messageId: "notCamelCase",
+                    data: { name: "a_global_variable" },
+                    type: "Identifier"
+                }
+            ]
+        },
         {
             code: "export * as snake_cased from 'mod'",
             parserOptions: { ecmaVersion: 2020, sourceType: "module" },
@@ -957,6 +1306,20 @@ ruleTester.run("camelcase", rule, {
                     type: "Identifier"
                 }
             ]
+        },
+
+        // Optional chaining.
+        {
+            code: "obj.o_k.non_camelcase = 0",
+            options: [{ properties: "always" }],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "notCamelCase", data: { name: "non_camelcase" } }]
+        },
+        {
+            code: "(obj?.o_k).non_camelcase = 0",
+            options: [{ properties: "always" }],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "notCamelCase", data: { name: "non_camelcase" } }]
         }
     ]
 });
index b0ecef6eb8106ff749f22b0d2a2c06e382b6d5b9..a1e5834243ee440f0d411c892cc79f2587be4947 100644 (file)
@@ -1906,6 +1906,28 @@ ruleTester.run("computed-property-spacing", rule, {
                     endColumn: 19
                 }
             ]
+        },
+
+        // Optional chaining
+        {
+            code: "obj?.[1];",
+            output: "obj?.[ 1 ];",
+            options: ["always"],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [
+                { messageId: "missingSpaceAfter", data: { tokenValue: "[" } },
+                { messageId: "missingSpaceBefore", data: { tokenValue: "]" } }
+            ]
+        },
+        {
+            code: "obj?.[ 1 ];",
+            output: "obj?.[1];",
+            options: ["never"],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [
+                { messageId: "unexpectedSpaceAfter", data: { tokenValue: "[" } },
+                { messageId: "unexpectedSpaceBefore", data: { tokenValue: "]" } }
+            ]
         }
     ]
 });
index b6c223dec7d2deef92fed5b1bd9858dfc25cfc81..85e4471acc86166b15cd318e346c1c6f6af6996e 100644 (file)
@@ -16,7 +16,7 @@ const { RuleTester } = require("../../../lib/rule-tester");
 // Tests
 //------------------------------------------------------------------------------
 
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2021 } });
 
 ruleTester.run("constructor-super", rule, {
     valid: [
@@ -37,7 +37,19 @@ ruleTester.run("constructor-super", rule, {
         "class A extends B { constructor() { if (true) { super(); } else { super(); } } }",
         "class A extends (class B {}) { constructor() { super(); } }",
         "class A extends (B = C) { constructor() { super(); } }",
+        "class A extends (B &&= C) { constructor() { super(); } }",
+        "class A extends (B ||= C) { constructor() { super(); } }",
+        "class A extends (B ??= C) { constructor() { super(); } }",
+        "class A extends (B ||= 5) { constructor() { super(); } }",
+        "class A extends (B ??= 5) { constructor() { super(); } }",
         "class A extends (B || C) { constructor() { super(); } }",
+        "class A extends (5 && B) { constructor() { super(); } }",
+
+        // A future improvement could detect the left side as statically falsy, making this invalid.
+        "class A extends (false && B) { constructor() { super(); } }",
+        "class A extends (B || 5) { constructor() { super(); } }",
+        "class A extends (B ?? 5) { constructor() { super(); } }",
+
         "class A extends (a ? B : C) { constructor() { super(); } }",
         "class A extends (B, C) { constructor() { super(); } }",
 
@@ -88,7 +100,10 @@ ruleTester.run("constructor-super", rule, {
                     }
                 }
             }
-        `
+        `,
+
+        // Optional chaining
+        "class A extends obj?.prop { constructor() { super(); } }"
     ],
     invalid: [
 
@@ -109,6 +124,40 @@ ruleTester.run("constructor-super", rule, {
             code: "class A extends 'test' { constructor() { super(); } }",
             errors: [{ messageId: "badSuper", type: "CallExpression" }]
         },
+        {
+            code: "class A extends (B = 5) { constructor() { super(); } }",
+            errors: [{ messageId: "badSuper", type: "CallExpression" }]
+        },
+        {
+            code: "class A extends (B && 5) { constructor() { super(); } }",
+            errors: [{ messageId: "badSuper", type: "CallExpression" }]
+        },
+        {
+
+            // `B &&= 5` evaluates either to a falsy value of `B` (which, then, cannot be a constructor), or to '5'
+            code: "class A extends (B &&= 5) { constructor() { super(); } }",
+            errors: [{ messageId: "badSuper", type: "CallExpression" }]
+        },
+        {
+            code: "class A extends (B += C) { constructor() { super(); } }",
+            errors: [{ messageId: "badSuper", type: "CallExpression" }]
+        },
+        {
+            code: "class A extends (B -= C) { constructor() { super(); } }",
+            errors: [{ messageId: "badSuper", type: "CallExpression" }]
+        },
+        {
+            code: "class A extends (B **= C) { constructor() { super(); } }",
+            errors: [{ messageId: "badSuper", type: "CallExpression" }]
+        },
+        {
+            code: "class A extends (B |= C) { constructor() { super(); } }",
+            errors: [{ messageId: "badSuper", type: "CallExpression" }]
+        },
+        {
+            code: "class A extends (B &= C) { constructor() { super(); } }",
+            errors: [{ messageId: "badSuper", type: "CallExpression" }]
+        },
 
         // derived classes.
         {
index 1d5ee8c6e4f328e858f183e93e8925fe26e5f44a..155eec994dac7c15482d1c4c465ce10e7995dd45 100644 (file)
@@ -535,6 +535,30 @@ ruleTester.run("curly", rule, {
                 }
             ]
         },
+        {
+            code: "if (foo) if (bar) { baz() }",
+            output: "if (foo) if (bar)  baz() ",
+            options: ["multi"],
+            errors: [
+                {
+                    messageId: "unexpectedCurlyAfterCondition",
+                    data: { name: "if" },
+                    type: "IfStatement"
+                }
+            ]
+        },
+        {
+            code: "if (foo) if (bar) baz(); else if (quux) { quuux(); }",
+            output: "if (foo) if (bar) baz(); else if (quux)  quuux(); ",
+            options: ["multi"],
+            errors: [
+                {
+                    messageId: "unexpectedCurlyAfterCondition",
+                    data: { name: "if" },
+                    type: "IfStatement"
+                }
+            ]
+        },
         {
             code: "while (foo) { bar() }",
             output: "while (foo)  bar() ",
@@ -559,6 +583,18 @@ ruleTester.run("curly", rule, {
                 }
             ]
         },
+        {
+            code: "if (foo) if (bar); else { baz() }",
+            output: "if (foo) if (bar); else  baz() ",
+            options: ["multi"],
+            errors: [
+                {
+                    messageId: "unexpectedCurlyAfter",
+                    data: { name: "else" },
+                    type: "IfStatement"
+                }
+            ]
+        },
         {
             code: "if (true) { if (false) console.log(1) }",
             output: "if (true)  if (false) console.log(1) ",
@@ -981,6 +1017,18 @@ ruleTester.run("curly", rule, {
                 }
             ]
         },
+        {
+            code: "if (true) if (true) foo(); else { bar(); baz(); }",
+            output: "if (true) if (true) {foo();} else { bar(); baz(); }",
+            options: ["multi", "consistent"],
+            errors: [
+                {
+                    messageId: "missingCurlyAfterCondition",
+                    data: { name: "if" },
+                    type: "IfStatement"
+                }
+            ]
+        },
         {
             code: "do{foo();} while (bar)",
             output: "do foo(); while (bar)",
index 1a6ea37a733ca2117d55c7eec7ad84418359ab96..bd56e1fc797a47e01c087de479cdc33450b46714 100644 (file)
@@ -136,6 +136,68 @@ ruleTester.run("dot-location", rule, {
         {
             code: "(\na &&\nb()\n).toString()",
             options: ["object"]
+        },
+
+        // Optional chaining
+        {
+            code: "obj?.prop",
+            options: ["object"],
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "obj?.[key]",
+            options: ["object"],
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "obj?.\nprop",
+            options: ["object"],
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "obj\n?.[key]",
+            options: ["object"],
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "obj?.\n[key]",
+            options: ["object"],
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "obj?.[\nkey]",
+            options: ["object"],
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "obj?.prop",
+            options: ["property"],
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "obj?.[key]",
+            options: ["property"],
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "obj\n?.prop",
+            options: ["property"],
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "obj\n?.[key]",
+            options: ["property"],
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "obj?.\n[key]",
+            options: ["property"],
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "obj?.[\nkey]",
+            options: ["property"],
+            parserOptions: { ecmaVersion: 2020 }
         }
     ],
     invalid: [
@@ -169,6 +231,52 @@ ruleTester.run("dot-location", rule, {
             options: ["object"],
             errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }]
         },
+        {
+            code: "01\n.toExponential()",
+            output: "01.\ntoExponential()",
+            options: ["object"],
+            errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }]
+        },
+        {
+            code: "08\n.toExponential()",
+            output: "08 .\ntoExponential()",
+            options: ["object"],
+            errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }]
+        },
+        {
+            code: "0190\n.toExponential()",
+            output: "0190 .\ntoExponential()",
+            options: ["object"],
+            errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }]
+        },
+        {
+            code: "5_000\n.toExponential()",
+            output: "5_000 .\ntoExponential()",
+            options: ["object"],
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }]
+        },
+        {
+            code: "5_000_00\n.toExponential()",
+            output: "5_000_00 .\ntoExponential()",
+            options: ["object"],
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }]
+        },
+        {
+            code: "5.000_000\n.toExponential()",
+            output: "5.000_000.\ntoExponential()",
+            options: ["object"],
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }]
+        },
+        {
+            code: "0b1010_1010\n.toExponential()",
+            output: "0b1010_1010.\ntoExponential()",
+            options: ["object"],
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }]
+        },
         {
             code: "foo /* a */ . /* b */ \n /* c */ bar",
             output: "foo /* a */  /* b */ \n /* c */ .bar",
@@ -255,6 +363,29 @@ ruleTester.run("dot-location", rule, {
             output: "(5).\ntoExponential()",
             options: ["object"],
             errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }]
+        },
+
+        // Optional chaining
+        {
+            code: "obj\n?.prop",
+            output: "obj?.\nprop",
+            options: ["object"],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "expectedDotAfterObject" }]
+        },
+        {
+            code: "10\n?.prop",
+            output: "10?.\nprop",
+            options: ["object"],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "expectedDotAfterObject" }]
+        },
+        {
+            code: "obj?.\nprop",
+            output: "obj\n?.prop",
+            options: ["property"],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "expectedDotBeforeProperty" }]
         }
     ]
 });
index 5532ea3587060622cd8fdb033df9c697ee80a0fe..39f3a676b0b22ddb8004881b5b959af24ae2fb9e 100644 (file)
@@ -218,6 +218,93 @@ ruleTester.run("dot-notation", rule, {
             output: null, // `let["if"]()` is a syntax error because `let[` indicates a destructuring variable declaration
             options: [{ allowKeywords: false }],
             errors: [{ messageId: "useBrackets", data: { key: "if" } }]
+        },
+        {
+            code: "5['prop']",
+            output: "5 .prop",
+            errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+        },
+        {
+            code: "-5['prop']",
+            output: "-5 .prop",
+            errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+        },
+        {
+            code: "01['prop']",
+            output: "01.prop",
+            errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+        },
+        {
+            code: "01234567['prop']",
+            output: "01234567.prop",
+            errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+        },
+        {
+            code: "08['prop']",
+            output: "08 .prop",
+            errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+        },
+        {
+            code: "090['prop']",
+            output: "090 .prop",
+            errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+        },
+        {
+            code: "018['prop']",
+            output: "018 .prop",
+            errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+        },
+        {
+            code: "5_000['prop']",
+            output: "5_000 .prop",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+        },
+        {
+            code: "5_000_00['prop']",
+            output: "5_000_00 .prop",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+        },
+        {
+            code: "5.000_000['prop']",
+            output: "5.000_000.prop",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+        },
+        {
+            code: "0b1010_1010['prop']",
+            output: "0b1010_1010.prop",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+        },
+
+        // Optional chaining
+        {
+            code: "obj?.['prop']",
+            output: "obj?.prop",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+        },
+        {
+            code: "0?.['prop']",
+            output: "0?.prop",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "useDot", data: { key: q("prop") } }]
+        },
+        {
+            code: "obj?.true",
+            output: "obj?.[\"true\"]",
+            options: [{ allowKeywords: false }],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "useBrackets", data: { key: "true" } }]
+        },
+        {
+            code: "let?.true",
+            output: "let?.[\"true\"]",
+            options: [{ allowKeywords: false }],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "useBrackets", data: { key: "true" } }]
         }
     ]
 });
index f0199fd0a4e019a39a5319589f9fdfd63648c199..3520882b019659a5da2b034ddfe7a067881f3000 100644 (file)
@@ -218,6 +218,28 @@ ruleTester.run("func-call-spacing", rule, {
             code: "import\n(source)",
             options: ["always", { allowNewlines: true }],
             parserOptions: { ecmaVersion: 2020 }
+        },
+
+        // Optional chaining
+        {
+            code: "func?.()",
+            options: ["never"],
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "func ?.()",
+            options: ["always"],
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "func?. ()",
+            options: ["always"],
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "func ?. ()",
+            options: ["always"],
+            parserOptions: { ecmaVersion: 2020 }
         }
     ],
     invalid: [
@@ -560,7 +582,7 @@ ruleTester.run("func-call-spacing", rule, {
         },
         {
             code: "f\n();",
-            output: "f ();",
+            output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787)
             options: ["always"],
             errors: [{ messageId: "unexpectedNewline", type: "CallExpression" }]
         },
@@ -572,7 +594,7 @@ ruleTester.run("func-call-spacing", rule, {
         },
         {
             code: "f\n(a, b);",
-            output: "f (a, b);",
+            output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787)
             options: ["always"],
             errors: [{ messageId: "unexpectedNewline", type: "CallExpression" }]
         },
@@ -593,7 +615,7 @@ ruleTester.run("func-call-spacing", rule, {
         },
         {
             code: "f.b\n();",
-            output: "f.b ();",
+            output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787)
             options: ["always"],
             errors: [
                 {
@@ -614,7 +636,7 @@ ruleTester.run("func-call-spacing", rule, {
         },
         {
             code: "f.b\n().c ();",
-            output: "f.b ().c ();",
+            output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787)
             options: ["always"],
             errors: [
                 {
@@ -635,13 +657,13 @@ ruleTester.run("func-call-spacing", rule, {
         },
         {
             code: "f\n() ()",
-            output: "f () ()",
+            output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787)
             options: ["always"],
             errors: [{ messageId: "unexpectedNewline", type: "CallExpression" }]
         },
         {
             code: "f\n()()",
-            output: "f () ()",
+            output: "f\n() ()", // Don't fix the first error to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787)
             options: ["always"],
             errors: [
                 { messageId: "unexpectedNewline", type: "CallExpression" },
@@ -696,25 +718,25 @@ ruleTester.run("func-call-spacing", rule, {
         },
         {
             code: "f\r();",
-            output: "f ();",
+            output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787)
             options: ["always"],
             errors: [{ messageId: "unexpectedNewline", type: "CallExpression" }]
         },
         {
             code: "f\u2028();",
-            output: "f ();",
+            output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787)
             options: ["always"],
             errors: [{ messageId: "unexpectedNewline", type: "CallExpression" }]
         },
         {
             code: "f\u2029();",
-            output: "f ();",
+            output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787)
             options: ["always"],
             errors: [{ messageId: "unexpectedNewline", type: "CallExpression" }]
         },
         {
             code: "f\r\n();",
-            output: "f ();",
+            output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787)
             options: ["always"],
             errors: [{ messageId: "unexpectedNewline", type: "CallExpression" }]
         },
@@ -841,7 +863,7 @@ ruleTester.run("func-call-spacing", rule, {
         },
         {
             code: "fnn\n (a, b);",
-            output: "fnn (a, b);",
+            output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787)
             options: ["always"],
             errors: [
                 {
@@ -853,6 +875,96 @@ ruleTester.run("func-call-spacing", rule, {
                     endColumn: 2
                 }
             ]
+        },
+        {
+            code: "f /*comment*/ ()",
+            output: null, // Don't remove comments
+            options: ["never"],
+            errors: [{ messageId: "unexpectedWhitespace" }]
+        },
+        {
+            code: "f /*\n*/ ()",
+            output: null, // Don't remove comments
+            options: ["never"],
+            errors: [{ messageId: "unexpectedWhitespace" }]
+        },
+        {
+            code: "f/*comment*/()",
+            output: "f/*comment*/ ()",
+            options: ["always"],
+            errors: [{ messageId: "missing" }]
+        },
+
+        // Optional chaining
+        {
+            code: "func ?.()",
+            output: "func?.()",
+            options: ["never"],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpectedWhitespace" }]
+        },
+        {
+            code: "func?. ()",
+            output: "func?.()",
+            options: ["never"],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpectedWhitespace" }]
+        },
+        {
+            code: "func ?. ()",
+            output: "func?.()",
+            options: ["never"],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpectedWhitespace" }]
+        },
+        {
+            code: "func\n?.()",
+            output: "func?.()",
+            options: ["never"],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpectedWhitespace" }]
+        },
+        {
+            code: "func\n//comment\n?.()",
+            output: null, // Don't remove comments
+            options: ["never"],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpectedWhitespace" }]
+        },
+        {
+            code: "func?.()",
+            output: null, // Not sure inserting a space into either before/after `?.`.
+            options: ["always"],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "missing" }]
+        },
+        {
+            code: "func\n  ?.()",
+            output: "func ?.()",
+            options: ["always"],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpectedNewline" }]
+        },
+        {
+            code: "func?.\n  ()",
+            output: "func?. ()",
+            options: ["always"],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpectedNewline" }]
+        },
+        {
+            code: "func  ?.\n  ()",
+            output: "func ?. ()",
+            options: ["always"],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpectedNewline" }]
+        },
+        {
+            code: "func\n /*comment*/ ?.()",
+            output: null, // Don't remove comments
+            options: ["always"],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpectedNewline" }]
         }
     ]
 });
index 72ee66b41ed6e3d3172da7fb46c12f48007ea296..908e9108f3ea957edfd53369ef608f195c63dbde 100644 (file)
@@ -29,6 +29,9 @@ ruleTester.run("func-name-matching", rule, {
         "foo = function foo() {};",
         { code: "foo = function foo() {};", options: ["always"] },
         { code: "foo = function bar() {};", options: ["never"] },
+        { code: "foo &&= function foo() {};", parserOptions: { ecmaVersion: 2021 } },
+        { code: "obj.foo ||= function foo() {};", parserOptions: { ecmaVersion: 2021 } },
+        { code: "obj['foo'] ??= function foo() {};", parserOptions: { ecmaVersion: 2021 } },
         "obj.foo = function foo() {};",
         { code: "obj.foo = function foo() {};", options: ["always"] },
         { code: "obj.foo = function bar() {};", options: ["never"] },
@@ -284,6 +287,27 @@ ruleTester.run("func-name-matching", rule, {
                 { messageId: "matchVariable", data: { funcName: "bar", name: "foo" } }
             ]
         },
+        {
+            code: "foo &&= function bar() {};",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [
+                { messageId: "matchVariable", data: { funcName: "bar", name: "foo" } }
+            ]
+        },
+        {
+            code: "obj.foo ||= function bar() {};",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [
+                { messageId: "matchProperty", data: { funcName: "bar", name: "foo" } }
+            ]
+        },
+        {
+            code: "obj['foo'] ??= function bar() {};",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [
+                { messageId: "matchProperty", data: { funcName: "bar", name: "foo" } }
+            ]
+        },
         {
             code: "obj.foo = function bar() {};",
             parserOptions: { ecmaVersion: 6 },
@@ -457,6 +481,79 @@ ruleTester.run("func-name-matching", rule, {
             errors: [
                 { messageId: "matchProperty", data: { funcName: "bar", name: "value" } }
             ]
+        },
+
+        // Optional chaining
+        {
+            code: "(obj?.aaa).foo = function bar() {};",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [
+                { messageId: "matchProperty", data: { funcName: "bar", name: "foo" } }
+            ]
+        },
+        {
+            code: "Object?.defineProperty(foo, 'bar', { value: function baz() {} })",
+            options: ["always", { considerPropertyDescriptor: true }],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [
+                { messageId: "matchProperty", data: { funcName: "baz", name: "bar" } }
+            ]
+        },
+        {
+            code: "(Object?.defineProperty)(foo, 'bar', { value: function baz() {} })",
+            options: ["always", { considerPropertyDescriptor: true }],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [
+                { messageId: "matchProperty", data: { funcName: "baz", name: "bar" } }
+            ]
+        },
+        {
+            code: "Object?.defineProperty(foo, 'bar', { value: function bar() {} })",
+            options: ["never", { considerPropertyDescriptor: true }],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [
+                { messageId: "notMatchProperty", data: { funcName: "bar", name: "bar" } }
+            ]
+        },
+        {
+            code: "(Object?.defineProperty)(foo, 'bar', { value: function bar() {} })",
+            options: ["never", { considerPropertyDescriptor: true }],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [
+                { messageId: "notMatchProperty", data: { funcName: "bar", name: "bar" } }
+            ]
+        },
+        {
+            code: "Object?.defineProperties(foo, { bar: { value: function baz() {} } })",
+            options: ["always", { considerPropertyDescriptor: true }],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [
+                { messageId: "matchProperty", data: { funcName: "baz", name: "bar" } }
+            ]
+        },
+        {
+            code: "(Object?.defineProperties)(foo, { bar: { value: function baz() {} } })",
+            options: ["always", { considerPropertyDescriptor: true }],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [
+                { messageId: "matchProperty", data: { funcName: "baz", name: "bar" } }
+            ]
+        },
+        {
+            code: "Object?.defineProperties(foo, { bar: { value: function bar() {} } })",
+            options: ["never", { considerPropertyDescriptor: true }],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [
+                { messageId: "notMatchProperty", data: { funcName: "bar", name: "bar" } }
+            ]
+        },
+        {
+            code: "(Object?.defineProperties)(foo, { bar: { value: function bar() {} } })",
+            options: ["never", { considerPropertyDescriptor: true }],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [
+                { messageId: "notMatchProperty", data: { funcName: "bar", name: "bar" } }
+            ]
         }
     ]
 });
index f918a38ba831f7a38a26b2c9850d13249e205b36..0c9feb7a7897139569306ac4333785a42ab63f5b 100644 (file)
@@ -87,6 +87,23 @@ ruleTester.run("function-paren-newline", rule, {
             code: "function baz(foo, bar) {}",
             options: ["multiline"]
         },
+        {
+            code: "async (foo, bar) => {};",
+            parserOptions: { ecmaVersion: 2017 }
+        },
+        {
+            code: `
+                async (
+                    foo,
+                    bar
+                ) => {};
+            `,
+            parserOptions: { ecmaVersion: 2017 }
+        },
+        {
+            code: "async foo => {};",
+            parserOptions: { ecmaVersion: 2017 }
+        },
         {
             code: "import(source)",
             parserOptions: { ecmaVersion: 2020 }
@@ -249,6 +266,40 @@ ruleTester.run("function-paren-newline", rule, {
             code: "new (Foo)",
             options: ["multiline-arguments"]
         },
+        {
+            code: "async (foo, bar) => {};",
+            options: ["multiline-arguments"],
+            parserOptions: { ecmaVersion: 2017 }
+        },
+        {
+            code: "async (foo) => {};",
+            options: ["multiline-arguments"],
+            parserOptions: { ecmaVersion: 2017 }
+        },
+        {
+            code: `
+                async (
+                    foo
+                ) => {};
+            `,
+            options: ["multiline-arguments"],
+            parserOptions: { ecmaVersion: 2017 }
+        },
+        {
+            code: `
+                async (
+                    foo,
+                    bar
+                ) => {};
+            `,
+            options: ["multiline-arguments"],
+            parserOptions: { ecmaVersion: 2017 }
+        },
+        {
+            code: "async foo => {};",
+            options: ["multiline-arguments"],
+            parserOptions: { ecmaVersion: 2017 }
+        },
         {
             code: "import(source)",
             options: ["multiline-arguments"],
@@ -329,6 +380,30 @@ ruleTester.run("function-paren-newline", rule, {
             `,
             options: ["always"]
         },
+        {
+            code: `
+                async (
+                    foo
+                ) => {};
+            `,
+            options: ["always"],
+            parserOptions: { ecmaVersion: 2017 }
+        },
+        {
+            code: `
+                async (
+                    foo,
+                    bar
+                ) => {};
+            `,
+            options: ["always"],
+            parserOptions: { ecmaVersion: 2017 }
+        },
+        {
+            code: "async foo => {};",
+            options: ["always"],
+            parserOptions: { ecmaVersion: 2017 }
+        },
         {
             code: "import(\n  source\n)",
             options: ["always"],
@@ -360,6 +435,16 @@ ruleTester.run("function-paren-newline", rule, {
             code: "function baz() {}",
             options: ["never"]
         },
+        {
+            code: "async (foo, bar) => {};",
+            options: ["never"],
+            parserOptions: { ecmaVersion: 2017 }
+        },
+        {
+            code: "async foo => {};",
+            options: ["never"],
+            parserOptions: { ecmaVersion: 2017 }
+        },
         {
             code: "import(source)",
             options: ["never"],
@@ -391,6 +476,27 @@ ruleTester.run("function-paren-newline", rule, {
             code: "baz(foo, bar);",
             options: [{ minItems: 3 }]
         },
+        {
+            code: "async (foo, bar) => {};",
+            options: [{ minItems: 3 }],
+            parserOptions: { ecmaVersion: 2017 }
+        },
+        {
+            code: `
+                async (
+                    foo,
+                    bar,
+                    baz
+                ) => {};
+            `,
+            options: [{ minItems: 3 }],
+            parserOptions: { ecmaVersion: 2017 }
+        },
+        {
+            code: "async foo => {};",
+            options: [{ minItems: 3 }],
+            parserOptions: { ecmaVersion: 2017 }
+        },
         {
             code: "import(source)",
             options: [{ minItems: 3 }],
@@ -431,6 +537,43 @@ ruleTester.run("function-paren-newline", rule, {
             `,
             options: ["consistent"]
         },
+        {
+            code: "async (foo, bar) => {};",
+            options: ["consistent"],
+            parserOptions: { ecmaVersion: 2017 }
+        },
+        {
+            code: "async foo => {};",
+            options: ["consistent"],
+            parserOptions: { ecmaVersion: 2017 }
+        },
+        {
+            code: `
+                async (foo,
+                    bar) => {};
+            `,
+            options: ["consistent"],
+            parserOptions: { ecmaVersion: 2017 }
+        },
+        {
+            code: `
+                async (
+                    foo, bar
+                ) => {};
+            `,
+            options: ["consistent"],
+            parserOptions: { ecmaVersion: 2017 }
+        },
+        {
+            code: `
+                async (
+                    foo,
+                    bar
+                ) => {};
+            `,
+            options: ["consistent"],
+            parserOptions: { ecmaVersion: 2017 }
+        },
         {
             code: "import(source)",
             options: ["consistent"],
@@ -564,6 +707,55 @@ ruleTester.run("function-paren-newline", rule, {
             output: null,
             errors: [RIGHT_UNEXPECTED_ERROR]
         },
+        {
+            code: `
+                async (
+                    foo, bar
+                ) => {};
+            `,
+            output: `
+                async (foo, bar) => {};
+            `,
+            parserOptions: { ecmaVersion: 2017 },
+            errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR]
+        },
+        {
+            code: `
+                async (foo, bar
+                ) => {};
+            `,
+            output: `
+                async (foo, bar) => {};
+            `,
+            parserOptions: { ecmaVersion: 2017 },
+            errors: [RIGHT_UNEXPECTED_ERROR]
+        },
+        {
+            code: `
+                async (foo,
+                    bar) => {};
+            `,
+            output: `
+                async (\nfoo,
+                    bar\n) => {};
+            `,
+            parserOptions: { ecmaVersion: 2017 },
+            errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR]
+        },
+        {
+            code: `
+                async (
+                    foo,
+                    bar) => {};
+            `,
+            output: `
+                async (
+                    foo,
+                    bar\n) => {};
+            `,
+            parserOptions: { ecmaVersion: 2017 },
+            errors: [RIGHT_MISSING_ERROR]
+        },
         {
             code: "import(\n  source\n)",
             output: "import(source)",
@@ -757,6 +949,46 @@ ruleTester.run("function-paren-newline", rule, {
             options: ["multiline-arguments"],
             errors: [RIGHT_UNEXPECTED_ERROR]
         },
+        {
+            code: `
+                async (foo, bar
+                ) => {};
+            `,
+            output: `
+                async (foo, bar) => {};
+            `,
+            options: ["multiline-arguments"],
+            parserOptions: { ecmaVersion: 2017 },
+            errors: [RIGHT_UNEXPECTED_ERROR]
+        },
+        {
+            code: `
+                async (foo,
+                    bar) => {};
+            `,
+            output: `
+                async (\nfoo,
+                    bar\n) => {};
+            `,
+            options: ["multiline-arguments"],
+            parserOptions: { ecmaVersion: 2017 },
+            errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR]
+        },
+        {
+            code: `
+                async (
+                    foo,
+                    bar) => {};
+            `,
+            output: `
+                async (
+                    foo,
+                    bar\n) => {};
+            `,
+            options: ["multiline-arguments"],
+            parserOptions: { ecmaVersion: 2017 },
+            errors: [RIGHT_MISSING_ERROR]
+        },
         {
             code: "import(source\n)",
             output: "import(source)",
@@ -849,6 +1081,45 @@ ruleTester.run("function-paren-newline", rule, {
             options: ["always"],
             errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR]
         },
+        {
+            code: `
+                async (foo, bar) => {};
+            `,
+            output: `
+                async (\nfoo, bar\n) => {};
+            `,
+            options: ["always"],
+            parserOptions: { ecmaVersion: 2017 },
+            errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR]
+        },
+        {
+            code: `
+                async (foo,
+                    bar) => {};
+            `,
+            output: `
+                async (\nfoo,
+                    bar\n) => {};
+            `,
+            options: ["always"],
+            parserOptions: { ecmaVersion: 2017 },
+            errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR]
+        },
+        {
+            code: `
+                async (
+                    foo,
+                    bar) => {};
+            `,
+            output: `
+                async (
+                    foo,
+                    bar\n) => {};
+            `,
+            options: ["always"],
+            parserOptions: { ecmaVersion: 2017 },
+            errors: [RIGHT_MISSING_ERROR]
+        },
         {
             code: "import(source)",
             output: "import(\nsource\n)",
@@ -965,6 +1236,35 @@ ruleTester.run("function-paren-newline", rule, {
             options: ["never"],
             errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR]
         },
+        {
+            code: `
+                async (
+                    foo,
+                    bar
+                ) => {};
+            `,
+            output: `
+                async (foo,
+                    bar) => {};
+            `,
+            options: ["never"],
+            parserOptions: { ecmaVersion: 2017 },
+            errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR]
+        },
+        {
+            code: `
+                async (
+                    foo,
+                    bar) => {};
+            `,
+            output: `
+                async (foo,
+                    bar) => {};
+            `,
+            options: ["never"],
+            parserOptions: { ecmaVersion: 2017 },
+            errors: [LEFT_UNEXPECTED_ERROR]
+        },
         {
             code: "import(\n  source\n)",
             output: "import(source)",
@@ -1012,6 +1312,46 @@ ruleTester.run("function-paren-newline", rule, {
             options: [{ minItems: 3 }],
             errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR]
         },
+        {
+            code: `
+                async (
+                    foo,
+                    bar
+                ) => {};
+            `,
+            output: `
+                async (foo,
+                    bar) => {};
+            `,
+            options: [{ minItems: 3 }],
+            parserOptions: { ecmaVersion: 2017 },
+            errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR]
+        },
+        {
+            code: `
+                async (
+                    foo,
+                    bar) => {};
+            `,
+            output: `
+                async (foo,
+                    bar) => {};
+            `,
+            options: [{ minItems: 3 }],
+            parserOptions: { ecmaVersion: 2017 },
+            errors: [LEFT_UNEXPECTED_ERROR]
+        },
+        {
+            code: `
+                async (foo, bar, baz) => {};
+            `,
+            output: `
+                async (\nfoo, bar, baz\n) => {};
+            `,
+            options: [{ minItems: 3 }],
+            parserOptions: { ecmaVersion: 2017 },
+            errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR]
+        },
         {
             code: "import(\n  source\n)",
             output: "import(source)",
@@ -1055,6 +1395,35 @@ ruleTester.run("function-paren-newline", rule, {
             options: ["consistent"],
             errors: [RIGHT_UNEXPECTED_ERROR]
         },
+        {
+            code: `
+                async (
+                    foo,
+                    bar) => {};
+            `,
+            output: `
+                async (
+                    foo,
+                    bar\n) => {};
+            `,
+            options: ["consistent"],
+            parserOptions: { ecmaVersion: 2017 },
+            errors: [RIGHT_MISSING_ERROR]
+        },
+        {
+            code: `
+                async (foo,
+                    bar
+                ) => {};
+            `,
+            output: `
+                async (foo,
+                    bar) => {};
+            `,
+            options: ["consistent"],
+            parserOptions: { ecmaVersion: 2017 },
+            errors: [RIGHT_UNEXPECTED_ERROR]
+        },
         {
             code: "import(source\n)",
             output: "import(source)",
index ea181650dee7836086e06515796e7ebd145c4179..c4092d86d2d20a0d31b517a72df3d2af14e020a5 100644 (file)
@@ -218,11 +218,35 @@ ruleTester.run("getter-return", rule, {
         },
         { code: "Object.defineProperty(foo, \"bar\", { get: function (){if(bar) {return true;}}});", errors: [{ messageId: "expectedAlways" }] },
         { code: "Object.defineProperty(foo, \"bar\", { get: function (){ ~function () { return true; }()}});", errors: [{ messageId: "expected" }] },
+
+        // option: {allowImplicit: true}
         { code: "Object.defineProperties(foo, { bar: { get: function () {}} });", options, errors: [{ messageId: "expected" }] },
         { code: "Object.defineProperties(foo, { bar: { get: function (){if(bar) {return true;}}}});", options, errors: [{ messageId: "expectedAlways" }] },
         { code: "Object.defineProperties(foo, { bar: { get: function () {~function () { return true; }()}} });", options, errors: [{ messageId: "expected" }] },
+        { code: "Object.defineProperty(foo, \"bar\", { get: function (){}});", options, errors: [{ messageId: "expected" }] },
 
-        // option: {allowImplicit: true}
-        { code: "Object.defineProperty(foo, \"bar\", { get: function (){}});", options, errors: [{ messageId: "expected" }] }
+        // Optional chaining
+        {
+            code: "Object?.defineProperty(foo, 'bar', { get: function (){} });",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "expected", data: { name: "method 'get'" } }]
+        },
+        {
+            code: "(Object?.defineProperty)(foo, 'bar', { get: function (){} });",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "expected", data: { name: "method 'get'" } }]
+        },
+        {
+            code: "Object?.defineProperty(foo, 'bar', { get: function (){} });",
+            options,
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "expected", data: { name: "method 'get'" } }]
+        },
+        {
+            code: "(Object?.defineProperty)(foo, 'bar', { get: function (){} });",
+            options,
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "expected", data: { name: "method 'get'" } }]
+        }
     ]
 });
index 3fb6ccca459df0feada82d22f539d70349087525..4993962bd05ad4d8adb29a446a76a0625729f6bb 100644 (file)
@@ -30,7 +30,13 @@ const valid = [
     { code: "var logger = require(DEBUG ? 'dev-logger' : 'logger');" },
     { code: "var logger = DEBUG ? require('dev-logger') : require('logger');" },
     { code: "function localScopedRequire(require) { require('y'); }" },
-    { code: "var someFunc = require('./someFunc'); someFunc(function(require) { return('bananas'); });" }
+    { code: "var someFunc = require('./someFunc'); someFunc(function(require) { return('bananas'); });" },
+
+    // Optional chaining
+    {
+        code: "var x = require('y')?.foo;",
+        parserOptions: { ecmaVersion: 2020 }
+    }
 ];
 
 const error = { messageId: "unexpected", type: "CallExpression" };
index 6c3f729e17ab0883aa9818ccc842dc38c236cb9d..4d13459acf6d9976370485e9e70f210a78eb4024 100644 (file)
@@ -17,7 +17,7 @@ const rule = require("../../../lib/rules/id-blacklist"),
 //------------------------------------------------------------------------------
 
 const ruleTester = new RuleTester();
-const error = { messageId: "blacklisted", type: "Identifier" };
+const error = { messageId: "restricted", type: "Identifier" };
 
 ruleTester.run("id-blacklist", rule, {
     valid: [
@@ -272,7 +272,7 @@ ruleTester.run("id-blacklist", rule, {
             options: ["bar"],
             parserOptions: { ecmaVersion: 6, sourceType: "module" },
             errors: [{
-                messageId: "blacklisted",
+                messageId: "restricted",
                 data: { name: "bar" },
                 type: "Identifier",
                 column: 17
@@ -283,7 +283,7 @@ ruleTester.run("id-blacklist", rule, {
             options: ["foo", "bar"],
             parserOptions: { ecmaVersion: 6, sourceType: "module" },
             errors: [{
-                messageId: "blacklisted",
+                messageId: "restricted",
                 data: { name: "bar" },
                 type: "Identifier",
                 column: 17
@@ -294,7 +294,7 @@ ruleTester.run("id-blacklist", rule, {
             options: ["foo"],
             parserOptions: { ecmaVersion: 6, sourceType: "module" },
             errors: [{
-                messageId: "blacklisted",
+                messageId: "restricted",
                 data: { name: "foo" },
                 type: "Identifier",
                 column: 17
@@ -305,7 +305,7 @@ ruleTester.run("id-blacklist", rule, {
             options: ["foo"],
             parserOptions: { ecmaVersion: 6, sourceType: "module" },
             errors: [{
-                messageId: "blacklisted",
+                messageId: "restricted",
                 data: { name: "foo" },
                 type: "Identifier",
                 column: 10
@@ -316,7 +316,7 @@ ruleTester.run("id-blacklist", rule, {
             options: ["foo"],
             parserOptions: { ecmaVersion: 6, sourceType: "module" },
             errors: [{
-                messageId: "blacklisted",
+                messageId: "restricted",
                 data: { name: "foo" },
                 type: "Identifier",
                 column: 22
@@ -327,7 +327,7 @@ ruleTester.run("id-blacklist", rule, {
             options: ["foo"],
             parserOptions: { ecmaVersion: 6, sourceType: "module" },
             errors: [{
-                messageId: "blacklisted",
+                messageId: "restricted",
                 data: { name: "foo" },
                 type: "Identifier",
                 column: 8
@@ -338,7 +338,7 @@ ruleTester.run("id-blacklist", rule, {
             options: ["bar"],
             parserOptions: { ecmaVersion: 6, sourceType: "module" },
             errors: [{
-                messageId: "blacklisted",
+                messageId: "restricted",
                 data: { name: "bar" },
                 type: "Identifier",
                 column: 26
@@ -350,13 +350,13 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6, sourceType: "module" },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "foo" },
                     type: "Identifier",
                     column: 5
                 },
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "foo" },
                     type: "Identifier",
                     column: 19
@@ -369,7 +369,7 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6, sourceType: "module" },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "foo" },
                     type: "Identifier",
                     column: 5
@@ -377,7 +377,7 @@ ruleTester.run("id-blacklist", rule, {
 
                 // reports each occurrence of local identifier, although it's renamed in this export specifier
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "foo" },
                     type: "Identifier",
                     column: 19
@@ -390,19 +390,19 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6, sourceType: "module" },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "foo" },
                     type: "Identifier",
                     column: 5
                 },
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "foo" },
                     type: "Identifier",
                     column: 19
                 },
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "foo" },
                     type: "Identifier",
                     column: 26
@@ -415,19 +415,19 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6, sourceType: "module" },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "foo" },
                     type: "Identifier",
                     column: 5
                 },
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "foo" },
                     type: "Identifier",
                     column: 19
                 },
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "bar" },
                     type: "Identifier",
                     column: 26
@@ -447,7 +447,7 @@ ruleTester.run("id-blacklist", rule, {
             options: ["bar"],
             parserOptions: { ecmaVersion: 6, sourceType: "module" },
             errors: [{
-                messageId: "blacklisted",
+                messageId: "restricted",
                 data: { name: "bar" },
                 type: "Identifier",
                 column: 17
@@ -458,7 +458,7 @@ ruleTester.run("id-blacklist", rule, {
             options: ["foo", "bar"],
             parserOptions: { ecmaVersion: 6, sourceType: "module" },
             errors: [{
-                messageId: "blacklisted",
+                messageId: "restricted",
                 data: { name: "bar" },
                 type: "Identifier",
                 column: 17
@@ -469,7 +469,7 @@ ruleTester.run("id-blacklist", rule, {
             options: ["foo"],
             parserOptions: { ecmaVersion: 6, sourceType: "module" },
             errors: [{
-                messageId: "blacklisted",
+                messageId: "restricted",
                 data: { name: "foo" },
                 type: "Identifier",
                 column: 17
@@ -480,7 +480,7 @@ ruleTester.run("id-blacklist", rule, {
             options: ["foo"],
             parserOptions: { ecmaVersion: 6, sourceType: "module" },
             errors: [{
-                messageId: "blacklisted",
+                messageId: "restricted",
                 data: { name: "foo" },
                 type: "Identifier",
                 column: 10
@@ -491,7 +491,7 @@ ruleTester.run("id-blacklist", rule, {
             options: ["foo"],
             parserOptions: { ecmaVersion: 6, sourceType: "module" },
             errors: [{
-                messageId: "blacklisted",
+                messageId: "restricted",
                 data: { name: "foo" },
                 type: "Identifier",
                 column: 22
@@ -508,7 +508,7 @@ ruleTester.run("id-blacklist", rule, {
             code: "foo[bar] = baz;",
             options: ["bar"],
             errors: [{
-                messageId: "blacklisted",
+                messageId: "restricted",
                 data: { name: "bar" },
                 type: "Identifier"
             }]
@@ -517,7 +517,7 @@ ruleTester.run("id-blacklist", rule, {
             code: "baz = foo[bar];",
             options: ["bar"],
             errors: [{
-                messageId: "blacklisted",
+                messageId: "restricted",
                 data: { name: "bar" },
                 type: "Identifier"
             }]
@@ -633,7 +633,7 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "foo" },
                     type: "Identifier",
                     column: 8
@@ -646,7 +646,7 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "bar" },
                     type: "Identifier",
                     column: 13
@@ -659,13 +659,13 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "foo" },
                     type: "Identifier",
                     column: 9
                 },
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "bar" },
                     type: "Identifier",
                     column: 15
@@ -678,7 +678,7 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "baz" },
                     type: "Identifier",
                     column: 19
@@ -691,13 +691,13 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "bar" },
                     type: "Identifier",
                     column: 15
                 },
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "baz" },
                     type: "Identifier",
                     column: 21
@@ -710,19 +710,19 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "foo" },
                     type: "Identifier",
                     column: 9
                 },
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "bar" },
                     type: "Identifier",
                     column: 17
                 },
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "baz" },
                     type: "Identifier",
                     column: 23
@@ -735,7 +735,7 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "baz" },
                     type: "Identifier",
                     column: 21
@@ -748,7 +748,7 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "qux" },
                     type: "Identifier",
                     column: 27
@@ -761,7 +761,7 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "bar" },
                     type: "Identifier",
                     column: 12
@@ -774,7 +774,7 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "baz" },
                     type: "Identifier",
                     column: 24
@@ -787,13 +787,13 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "foo" },
                     type: "Identifier",
                     column: 4
                 },
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "bar" },
                     type: "Identifier",
                     column: 14
@@ -806,7 +806,7 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "bar" },
                     type: "Identifier",
                     column: 17
@@ -819,7 +819,7 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "bar" },
                     type: "Identifier",
                     column: 10
@@ -832,7 +832,7 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "baz" },
                     type: "Identifier",
                     column: 18
@@ -845,7 +845,7 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "bar" },
                     type: "Identifier",
                     column: 10
@@ -858,7 +858,7 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "bar" },
                     type: "Identifier",
                     column: 11
@@ -871,7 +871,7 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "bar" },
                     type: "Identifier",
                     column: 17
@@ -884,7 +884,7 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "bar" },
                     type: "Identifier",
                     column: 19
@@ -897,7 +897,7 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 9 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "bar" },
                     type: "Identifier",
                     column: 10
@@ -910,7 +910,7 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "bar" },
                     type: "Identifier",
                     column: 7
@@ -923,7 +923,7 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "bar" },
                     type: "Identifier",
                     column: 8
@@ -937,7 +937,7 @@ ruleTester.run("id-blacklist", rule, {
             options: ["undefined"],
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "undefined" },
                     type: "Identifier"
                 }
@@ -948,7 +948,7 @@ ruleTester.run("id-blacklist", rule, {
             options: ["undefined"],
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "undefined" },
                     type: "Identifier"
                 }
@@ -959,7 +959,7 @@ ruleTester.run("id-blacklist", rule, {
             options: ["undefined"],
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "undefined" },
                     type: "Identifier",
                     column: 13
@@ -972,7 +972,7 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "Number" },
                     type: "Identifier"
                 }
@@ -984,7 +984,7 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "Number" },
                     type: "Identifier"
                 }
@@ -996,13 +996,13 @@ ruleTester.run("id-blacklist", rule, {
             globals: { myGlobal: "readonly" },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "myGlobal" },
                     type: "Identifier",
                     column: 1
                 },
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "myGlobal" },
                     type: "Identifier",
                     column: 30
@@ -1017,13 +1017,13 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "foo" },
                     type: "Identifier",
                     column: 7
                 },
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "foo" },
                     type: "Identifier",
                     column: 22
@@ -1036,13 +1036,13 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "foo" },
                     type: "Identifier",
                     column: 5
                 },
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "foo" },
                     type: "Identifier",
                     column: 10
@@ -1054,13 +1054,13 @@ ruleTester.run("id-blacklist", rule, {
             options: ["foo"],
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "foo" },
                     type: "Identifier",
                     column: 7
                 },
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "foo" },
                     type: "Identifier",
                     column: 16
@@ -1072,13 +1072,13 @@ ruleTester.run("id-blacklist", rule, {
             options: ["foo"],
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "foo" },
                     type: "Identifier",
                     column: 10
                 },
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "foo" },
                     type: "Identifier",
                     column: 29
@@ -1091,13 +1091,13 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "Foo" },
                     type: "Identifier",
                     column: 7
                 },
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "Foo" },
                     type: "Identifier",
                     column: 24
@@ -1112,13 +1112,13 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "undefined" },
                     type: "Identifier",
                     column: 5
                 },
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "undefined" },
                     type: "Identifier",
                     column: 16
@@ -1130,13 +1130,13 @@ ruleTester.run("id-blacklist", rule, {
             options: ["undefined"],
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "undefined" },
                     type: "Identifier",
                     column: 7
                 },
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "undefined" },
                     type: "Identifier",
                     column: 22
@@ -1148,13 +1148,13 @@ ruleTester.run("id-blacklist", rule, {
             options: ["undefined"],
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "undefined" },
                     type: "Identifier",
                     column: 10
                 },
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "undefined" },
                     type: "Identifier",
                     column: 28
@@ -1167,13 +1167,13 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "Number" },
                     type: "Identifier",
                     column: 7
                 },
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "Number" },
                     type: "Identifier",
                     column: 21
@@ -1182,8 +1182,8 @@ ruleTester.run("id-blacklist", rule, {
         },
 
         /*
-         * Assignment to a property with a blacklisted name isn't allowed, in general.
-         * In this case, that restriction prevents creating a global variable with a blacklisted name.
+         * Assignment to a property with a restricted name isn't allowed, in general.
+         * In this case, that restriction prevents creating a global variable with a restricted name.
          */
         {
             code: "/* globals myGlobal */ window.myGlobal = 5; foo = myGlobal;",
@@ -1191,7 +1191,7 @@ ruleTester.run("id-blacklist", rule, {
             env: { browser: true },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "myGlobal" },
                     type: "Identifier",
                     column: 31
@@ -1206,7 +1206,7 @@ ruleTester.run("id-blacklist", rule, {
             globals: { undefined: "off" },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "undefined" },
                     type: "Identifier"
                 }
@@ -1217,7 +1217,7 @@ ruleTester.run("id-blacklist", rule, {
             options: ["Number"],
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "Number" },
                     type: "Identifier"
                 }
@@ -1228,7 +1228,7 @@ ruleTester.run("id-blacklist", rule, {
             options: ["Map"],
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "Map" },
                     type: "Identifier"
                 }
@@ -1242,13 +1242,13 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "undefined" },
                     type: "Identifier",
                     column: 16
                 },
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "undefined" },
                     type: "Identifier",
                     column: 33
@@ -1260,13 +1260,13 @@ ruleTester.run("id-blacklist", rule, {
             options: ["Number"],
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "Number" },
                     type: "Identifier",
                     column: 14
                 },
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "Number" },
                     type: "Identifier",
                     column: 32
@@ -1279,13 +1279,13 @@ ruleTester.run("id-blacklist", rule, {
             globals: { myGlobal: "readonly" },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "myGlobal" },
                     type: "Identifier",
                     column: 22
                 },
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "myGlobal" },
                     type: "Identifier",
                     column: 36
@@ -1298,13 +1298,13 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6, sourceType: "module" },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "Number" },
                     type: "Identifier",
                     column: 28
                 },
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "Number" },
                     type: "Identifier",
                     column: 58
@@ -1317,13 +1317,13 @@ ruleTester.run("id-blacklist", rule, {
             parserOptions: { ecmaVersion: 6, sourceType: "module" },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "Number" },
                     type: "Identifier",
                     column: 8
                 },
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "Number" },
                     type: "Identifier",
                     column: 44
@@ -1335,21 +1335,21 @@ ruleTester.run("id-blacklist", rule, {
             options: ["undefined"],
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "undefined" },
                     type: "Identifier"
                 }
             ]
         },
 
-        // this is a reference to a global variable, but at the same time creates a property with a blacklisted name
+        // this is a reference to a global variable, but at the same time creates a property with a restricted name
         {
             code: "var foo = { undefined }",
             options: ["undefined"],
             parserOptions: { ecmaVersion: 6 },
             errors: [
                 {
-                    messageId: "blacklisted",
+                    messageId: "restricted",
                     data: { name: "undefined" },
                     type: "Identifier"
                 }
diff --git a/eslint/tests/lib/rules/id-denylist.js b/eslint/tests/lib/rules/id-denylist.js
new file mode 100644 (file)
index 0000000..6da179d
--- /dev/null
@@ -0,0 +1,1359 @@
+/**
+ * @fileoverview Tests for id-denylist rule.
+ * @author Keith Cirkel
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const rule = require("../../../lib/rules/id-denylist"),
+    { RuleTester } = require("../../../lib/rule-tester");
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester();
+const error = { messageId: "restricted", type: "Identifier" };
+
+ruleTester.run("id-denylist", rule, {
+    valid: [
+        {
+            code: "foo = \"bar\"",
+            options: ["bar"]
+        },
+        {
+            code: "bar = \"bar\"",
+            options: ["foo"]
+        },
+        {
+            code: "foo = \"bar\"",
+            options: ["f", "fo", "fooo", "bar"]
+        },
+        {
+            code: "function foo(){}",
+            options: ["bar"]
+        },
+        {
+            code: "foo()",
+            options: ["f", "fo", "fooo", "bar"]
+        },
+        {
+            code: "import { foo as bar } from 'mod'",
+            options: ["foo"],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" }
+        },
+        {
+            code: "export { foo as bar } from 'mod'",
+            options: ["foo"],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" }
+        },
+        {
+            code: "foo.bar()",
+            options: ["f", "fo", "fooo", "b", "ba", "baz"]
+        },
+        {
+            code: "var foo = bar.baz;",
+            options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz"]
+        },
+        {
+            code: "var foo = bar.baz.bing;",
+            options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"]
+        },
+        {
+            code: "foo.bar.baz = bing.bong.bash;",
+            options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"]
+        },
+        {
+            code: "if (foo.bar) {}",
+            options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"]
+        },
+        {
+            code: "var obj = { key: foo.bar };",
+            options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"]
+        },
+        {
+            code: "const {foo: bar} = baz",
+            options: ["foo"],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "const {foo: {bar: baz}} = qux",
+            options: ["foo", "bar"],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "function foo({ bar: baz }) {}",
+            options: ["bar"],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "function foo({ bar: {baz: qux} }) {}",
+            options: ["bar", "baz"],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "function foo({baz} = obj.qux) {}",
+            options: ["qux"],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "function foo({ foo: {baz} = obj.qux }) {}",
+            options: ["qux"],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "({a: bar = obj.baz});",
+            options: ["baz"],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "({foo: {a: bar = obj.baz}} = qux);",
+            options: ["baz"],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "var arr = [foo.bar];",
+            options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"]
+        },
+        {
+            code: "[foo.bar]",
+            options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"]
+        },
+        {
+            code: "[foo.bar.nesting]",
+            options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"]
+        },
+        {
+            code: "if (foo.bar === bar.baz) { [foo.bar] }",
+            options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"]
+        },
+        {
+            code: "var myArray = new Array(); var myDate = new Date();",
+            options: ["array", "date", "mydate", "myarray", "new", "var"]
+        },
+        {
+            code: "foo()",
+            options: ["foo"]
+        },
+        {
+            code: "foo.bar()",
+            options: ["bar"]
+        },
+        {
+            code: "foo.bar",
+            options: ["bar"]
+        },
+        {
+            code: "({foo: obj.bar.bar.bar.baz} = {});",
+            options: ["foo", "bar"],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "({[obj.bar]: a = baz} = qux);",
+            options: ["bar"],
+            parserOptions: { ecmaVersion: 6 }
+        },
+
+        // references to global variables
+        {
+            code: "Number.parseInt()",
+            options: ["Number"]
+        },
+        {
+            code: "x = Number.NaN;",
+            options: ["Number"]
+        },
+        {
+            code: "var foo = undefined;",
+            options: ["undefined"]
+        },
+        {
+            code: "if (foo === undefined);",
+            options: ["undefined"]
+        },
+        {
+            code: "obj[undefined] = 5;", // creates obj["undefined"]. It should be disallowed, but the rule doesn't know values of globals and can't control computed access.
+            options: ["undefined"]
+        },
+        {
+            code: "foo = { [myGlobal]: 1 };",
+            options: ["myGlobal"],
+            parserOptions: { ecmaVersion: 6 },
+            globals: { myGlobal: "readonly" }
+        },
+        {
+            code: "({ myGlobal } = foo);", // writability doesn't affect the logic, it's always assumed that user doesn't have control over the names of globals.
+            options: ["myGlobal"],
+            parserOptions: { ecmaVersion: 6 },
+            globals: { myGlobal: "writable" }
+        },
+        {
+            code: "/* global myGlobal: readonly */ myGlobal = 5;",
+            options: ["myGlobal"]
+        },
+        {
+            code: "var foo = [Map];",
+            options: ["Map"],
+            env: { es6: true }
+        },
+        {
+            code: "var foo = { bar: window.baz };",
+            options: ["window"],
+            env: { browser: true }
+        }
+    ],
+    invalid: [
+        {
+            code: "foo = \"bar\"",
+            options: ["foo"],
+            errors: [
+                error
+            ]
+        },
+        {
+            code: "bar = \"bar\"",
+            options: ["bar"],
+            errors: [
+                error
+            ]
+        },
+        {
+            code: "foo = \"bar\"",
+            options: ["f", "fo", "foo", "bar"],
+            errors: [
+                error
+            ]
+        },
+        {
+            code: "function foo(){}",
+            options: ["f", "fo", "foo", "bar"],
+            errors: [
+                error
+            ]
+        },
+        {
+            code: "import foo from 'mod'",
+            options: ["foo"],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [
+                error
+            ]
+        },
+        {
+            code: "import * as foo from 'mod'",
+            options: ["foo"],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [
+                error
+            ]
+        },
+        {
+            code: "export * as foo from 'mod'",
+            options: ["foo"],
+            parserOptions: { ecmaVersion: 2020, sourceType: "module" },
+            errors: [
+                error
+            ]
+        },
+        {
+            code: "import { foo } from 'mod'",
+            options: ["foo"],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [
+                error
+            ]
+        },
+        {
+            code: "import { foo as bar } from 'mod'",
+            options: ["bar"],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [{
+                messageId: "restricted",
+                data: { name: "bar" },
+                type: "Identifier",
+                column: 17
+            }]
+        },
+        {
+            code: "import { foo as bar } from 'mod'",
+            options: ["foo", "bar"],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [{
+                messageId: "restricted",
+                data: { name: "bar" },
+                type: "Identifier",
+                column: 17
+            }]
+        },
+        {
+            code: "import { foo as foo } from 'mod'",
+            options: ["foo"],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [{
+                messageId: "restricted",
+                data: { name: "foo" },
+                type: "Identifier",
+                column: 17
+            }]
+        },
+        {
+            code: "import { foo, foo as bar } from 'mod'",
+            options: ["foo"],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [{
+                messageId: "restricted",
+                data: { name: "foo" },
+                type: "Identifier",
+                column: 10
+            }]
+        },
+        {
+            code: "import { foo as bar, foo } from 'mod'",
+            options: ["foo"],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [{
+                messageId: "restricted",
+                data: { name: "foo" },
+                type: "Identifier",
+                column: 22
+            }]
+        },
+        {
+            code: "import foo, { foo as bar } from 'mod'",
+            options: ["foo"],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [{
+                messageId: "restricted",
+                data: { name: "foo" },
+                type: "Identifier",
+                column: 8
+            }]
+        },
+        {
+            code: "var foo; export { foo as bar };",
+            options: ["bar"],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [{
+                messageId: "restricted",
+                data: { name: "bar" },
+                type: "Identifier",
+                column: 26
+            }]
+        },
+        {
+            code: "var foo; export { foo };",
+            options: ["foo"],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "foo" },
+                    type: "Identifier",
+                    column: 5
+                },
+                {
+                    messageId: "restricted",
+                    data: { name: "foo" },
+                    type: "Identifier",
+                    column: 19
+                }
+            ]
+        },
+        {
+            code: "var foo; export { foo as bar };",
+            options: ["foo"],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "foo" },
+                    type: "Identifier",
+                    column: 5
+                },
+
+                // reports each occurrence of local identifier, although it's renamed in this export specifier
+                {
+                    messageId: "restricted",
+                    data: { name: "foo" },
+                    type: "Identifier",
+                    column: 19
+                }
+            ]
+        },
+        {
+            code: "var foo; export { foo as foo };",
+            options: ["foo"],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "foo" },
+                    type: "Identifier",
+                    column: 5
+                },
+                {
+                    messageId: "restricted",
+                    data: { name: "foo" },
+                    type: "Identifier",
+                    column: 19
+                },
+                {
+                    messageId: "restricted",
+                    data: { name: "foo" },
+                    type: "Identifier",
+                    column: 26
+                }
+            ]
+        },
+        {
+            code: "var foo; export { foo as bar };",
+            options: ["foo", "bar"],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "foo" },
+                    type: "Identifier",
+                    column: 5
+                },
+                {
+                    messageId: "restricted",
+                    data: { name: "foo" },
+                    type: "Identifier",
+                    column: 19
+                },
+                {
+                    messageId: "restricted",
+                    data: { name: "bar" },
+                    type: "Identifier",
+                    column: 26
+                }
+            ]
+        },
+        {
+            code: "export { foo } from 'mod'",
+            options: ["foo"],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [
+                error
+            ]
+        },
+        {
+            code: "export { foo as bar } from 'mod'",
+            options: ["bar"],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [{
+                messageId: "restricted",
+                data: { name: "bar" },
+                type: "Identifier",
+                column: 17
+            }]
+        },
+        {
+            code: "export { foo as bar } from 'mod'",
+            options: ["foo", "bar"],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [{
+                messageId: "restricted",
+                data: { name: "bar" },
+                type: "Identifier",
+                column: 17
+            }]
+        },
+        {
+            code: "export { foo as foo } from 'mod'",
+            options: ["foo"],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [{
+                messageId: "restricted",
+                data: { name: "foo" },
+                type: "Identifier",
+                column: 17
+            }]
+        },
+        {
+            code: "export { foo, foo as bar } from 'mod'",
+            options: ["foo"],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [{
+                messageId: "restricted",
+                data: { name: "foo" },
+                type: "Identifier",
+                column: 10
+            }]
+        },
+        {
+            code: "export { foo as bar, foo } from 'mod'",
+            options: ["foo"],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [{
+                messageId: "restricted",
+                data: { name: "foo" },
+                type: "Identifier",
+                column: 22
+            }]
+        },
+        {
+            code: "foo.bar()",
+            options: ["f", "fo", "foo", "b", "ba", "baz"],
+            errors: [
+                error
+            ]
+        },
+        {
+            code: "foo[bar] = baz;",
+            options: ["bar"],
+            errors: [{
+                messageId: "restricted",
+                data: { name: "bar" },
+                type: "Identifier"
+            }]
+        },
+        {
+            code: "baz = foo[bar];",
+            options: ["bar"],
+            errors: [{
+                messageId: "restricted",
+                data: { name: "bar" },
+                type: "Identifier"
+            }]
+        },
+        {
+            code: "var foo = bar.baz;",
+            options: ["f", "fo", "foo", "b", "ba", "barr", "bazz"],
+            errors: [
+                error
+            ]
+        },
+        {
+            code: "var foo = bar.baz;",
+            options: ["f", "fo", "fooo", "b", "ba", "bar", "bazz"],
+            errors: [
+                error
+            ]
+        },
+        {
+            code: "if (foo.bar) {}",
+            options: ["f", "fo", "foo", "b", "ba", "barr", "bazz", "bingg"],
+            errors: [
+                error
+            ]
+        },
+        {
+            code: "var obj = { key: foo.bar };",
+            options: ["obj"],
+            errors: [
+                error
+            ]
+        },
+        {
+            code: "var obj = { key: foo.bar };",
+            options: ["key"],
+            errors: [
+                error
+            ]
+        },
+        {
+            code: "var obj = { key: foo.bar };",
+            options: ["foo"],
+            errors: [
+                error
+            ]
+        },
+        {
+            code: "var arr = [foo.bar];",
+            options: ["arr"],
+            errors: [
+                error
+            ]
+        },
+        {
+            code: "var arr = [foo.bar];",
+            options: ["foo"],
+            errors: [
+                error
+            ]
+        },
+        {
+            code: "[foo.bar]",
+            options: ["f", "fo", "foo", "b", "ba", "barr", "bazz", "bingg"],
+            errors: [
+                error
+            ]
+        },
+        {
+            code: "if (foo.bar === bar.baz) { [bing.baz] }",
+            options: ["f", "fo", "foo", "b", "ba", "barr", "bazz", "bingg"],
+            errors: [
+                error
+            ]
+        },
+        {
+            code: "if (foo.bar === bar.baz) { [foo.bar] }",
+            options: ["f", "fo", "fooo", "b", "ba", "bar", "bazz", "bingg"],
+            errors: [
+                error
+            ]
+        },
+        {
+            code: "var myArray = new Array(); var myDate = new Date();",
+            options: ["array", "date", "myDate", "myarray", "new", "var"],
+            errors: [
+                error
+            ]
+        },
+        {
+            code: "var myArray = new Array(); var myDate = new Date();",
+            options: ["array", "date", "mydate", "myArray", "new", "var"],
+            errors: [
+                error
+            ]
+        },
+        {
+            code: "foo.bar = 1",
+            options: ["bar"],
+            errors: [
+                error
+            ]
+        },
+        {
+            code: "foo.bar.baz = 1",
+            options: ["bar", "baz"],
+            errors: [
+                error
+            ]
+        },
+        {
+            code: "const {foo} = baz",
+            options: ["foo"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "foo" },
+                    type: "Identifier",
+                    column: 8
+                }
+            ]
+        },
+        {
+            code: "const {foo: bar} = baz",
+            options: ["foo", "bar"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "bar" },
+                    type: "Identifier",
+                    column: 13
+                }
+            ]
+        },
+        {
+            code: "const {[foo]: bar} = baz",
+            options: ["foo", "bar"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "foo" },
+                    type: "Identifier",
+                    column: 9
+                },
+                {
+                    messageId: "restricted",
+                    data: { name: "bar" },
+                    type: "Identifier",
+                    column: 15
+                }
+            ]
+        },
+        {
+            code: "const {foo: {bar: baz}} = qux",
+            options: ["foo", "bar", "baz"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "baz" },
+                    type: "Identifier",
+                    column: 19
+                }
+            ]
+        },
+        {
+            code: "const {foo: {[bar]: baz}} = qux",
+            options: ["foo", "bar", "baz"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "bar" },
+                    type: "Identifier",
+                    column: 15
+                },
+                {
+                    messageId: "restricted",
+                    data: { name: "baz" },
+                    type: "Identifier",
+                    column: 21
+                }
+            ]
+        },
+        {
+            code: "const {[foo]: {[bar]: baz}} = qux",
+            options: ["foo", "bar", "baz"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "foo" },
+                    type: "Identifier",
+                    column: 9
+                },
+                {
+                    messageId: "restricted",
+                    data: { name: "bar" },
+                    type: "Identifier",
+                    column: 17
+                },
+                {
+                    messageId: "restricted",
+                    data: { name: "baz" },
+                    type: "Identifier",
+                    column: 23
+                }
+            ]
+        },
+        {
+            code: "function foo({ bar: baz }) {}",
+            options: ["bar", "baz"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "baz" },
+                    type: "Identifier",
+                    column: 21
+                }
+            ]
+        },
+        {
+            code: "function foo({ bar: {baz: qux} }) {}",
+            options: ["bar", "baz", "qux"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "qux" },
+                    type: "Identifier",
+                    column: 27
+                }
+            ]
+        },
+        {
+            code: "({foo: obj.bar} = baz);",
+            options: ["foo", "bar"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "bar" },
+                    type: "Identifier",
+                    column: 12
+                }
+            ]
+        },
+        {
+            code: "({foo: obj.bar.bar.bar.baz} = {});",
+            options: ["foo", "bar", "baz"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "baz" },
+                    type: "Identifier",
+                    column: 24
+                }
+            ]
+        },
+        {
+            code: "({[foo]: obj.bar} = baz);",
+            options: ["foo", "bar"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "foo" },
+                    type: "Identifier",
+                    column: 4
+                },
+                {
+                    messageId: "restricted",
+                    data: { name: "bar" },
+                    type: "Identifier",
+                    column: 14
+                }
+            ]
+        },
+        {
+            code: "({foo: { a: obj.bar }} = baz);",
+            options: ["bar"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "bar" },
+                    type: "Identifier",
+                    column: 17
+                }
+            ]
+        },
+        {
+            code: "({a: obj.bar = baz} = qux);",
+            options: ["bar"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "bar" },
+                    type: "Identifier",
+                    column: 10
+                }
+            ]
+        },
+        {
+            code: "({a: obj.bar.bar.baz = obj.qux} = obj.qux);",
+            options: ["a", "bar", "baz", "qux"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "baz" },
+                    type: "Identifier",
+                    column: 18
+                }
+            ]
+        },
+        {
+            code: "({a: obj[bar] = obj.qux} = obj.qux);",
+            options: ["a", "bar", "baz", "qux"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "bar" },
+                    type: "Identifier",
+                    column: 10
+                }
+            ]
+        },
+        {
+            code: "({a: [obj.bar] = baz} = qux);",
+            options: ["bar"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "bar" },
+                    type: "Identifier",
+                    column: 11
+                }
+            ]
+        },
+        {
+            code: "({foo: { a: obj.bar = baz}} = qux);",
+            options: ["bar"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "bar" },
+                    type: "Identifier",
+                    column: 17
+                }
+            ]
+        },
+        {
+            code: "({foo: { [a]: obj.bar }} = baz);",
+            options: ["bar"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "bar" },
+                    type: "Identifier",
+                    column: 19
+                }
+            ]
+        },
+        {
+            code: "({...obj.bar} = baz);",
+            options: ["bar"],
+            parserOptions: { ecmaVersion: 9 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "bar" },
+                    type: "Identifier",
+                    column: 10
+                }
+            ]
+        },
+        {
+            code: "([obj.bar] = baz);",
+            options: ["bar"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "bar" },
+                    type: "Identifier",
+                    column: 7
+                }
+            ]
+        },
+        {
+            code: "const [bar] = baz;",
+            options: ["bar"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "bar" },
+                    type: "Identifier",
+                    column: 8
+                }
+            ]
+        },
+
+        // not a reference to a global variable, because it isn't a reference to a variable
+        {
+            code: "foo.undefined = 1;",
+            options: ["undefined"],
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "undefined" },
+                    type: "Identifier"
+                }
+            ]
+        },
+        {
+            code: "var foo = { undefined: 1 };",
+            options: ["undefined"],
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "undefined" },
+                    type: "Identifier"
+                }
+            ]
+        },
+        {
+            code: "var foo = { undefined: undefined };",
+            options: ["undefined"],
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "undefined" },
+                    type: "Identifier",
+                    column: 13
+                }
+            ]
+        },
+        {
+            code: "var foo = { Number() {} };",
+            options: ["Number"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "Number" },
+                    type: "Identifier"
+                }
+            ]
+        },
+        {
+            code: "class Foo { Number() {} }",
+            options: ["Number"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "Number" },
+                    type: "Identifier"
+                }
+            ]
+        },
+        {
+            code: "myGlobal: while(foo) { break myGlobal; } ",
+            options: ["myGlobal"],
+            globals: { myGlobal: "readonly" },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "myGlobal" },
+                    type: "Identifier",
+                    column: 1
+                },
+                {
+                    messageId: "restricted",
+                    data: { name: "myGlobal" },
+                    type: "Identifier",
+                    column: 30
+                }
+            ]
+        },
+
+        // globals declared in the given source code are not excluded from consideration
+        {
+            code: "const foo = 1; bar = foo;",
+            options: ["foo"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "foo" },
+                    type: "Identifier",
+                    column: 7
+                },
+                {
+                    messageId: "restricted",
+                    data: { name: "foo" },
+                    type: "Identifier",
+                    column: 22
+                }
+            ]
+        },
+        {
+            code: "let foo; foo = bar;",
+            options: ["foo"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "foo" },
+                    type: "Identifier",
+                    column: 5
+                },
+                {
+                    messageId: "restricted",
+                    data: { name: "foo" },
+                    type: "Identifier",
+                    column: 10
+                }
+            ]
+        },
+        {
+            code: "bar = foo; var foo;",
+            options: ["foo"],
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "foo" },
+                    type: "Identifier",
+                    column: 7
+                },
+                {
+                    messageId: "restricted",
+                    data: { name: "foo" },
+                    type: "Identifier",
+                    column: 16
+                }
+            ]
+        },
+        {
+            code: "function foo() {} var bar = foo;",
+            options: ["foo"],
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "foo" },
+                    type: "Identifier",
+                    column: 10
+                },
+                {
+                    messageId: "restricted",
+                    data: { name: "foo" },
+                    type: "Identifier",
+                    column: 29
+                }
+            ]
+        },
+        {
+            code: "class Foo {} var bar = Foo;",
+            options: ["Foo"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "Foo" },
+                    type: "Identifier",
+                    column: 7
+                },
+                {
+                    messageId: "restricted",
+                    data: { name: "Foo" },
+                    type: "Identifier",
+                    column: 24
+                }
+            ]
+        },
+
+        // redeclared globals are not excluded from consideration
+        {
+            code: "let undefined; undefined = 1;",
+            options: ["undefined"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "undefined" },
+                    type: "Identifier",
+                    column: 5
+                },
+                {
+                    messageId: "restricted",
+                    data: { name: "undefined" },
+                    type: "Identifier",
+                    column: 16
+                }
+            ]
+        },
+        {
+            code: "foo = undefined; var undefined;",
+            options: ["undefined"],
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "undefined" },
+                    type: "Identifier",
+                    column: 7
+                },
+                {
+                    messageId: "restricted",
+                    data: { name: "undefined" },
+                    type: "Identifier",
+                    column: 22
+                }
+            ]
+        },
+        {
+            code: "function undefined(){} x = undefined;",
+            options: ["undefined"],
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "undefined" },
+                    type: "Identifier",
+                    column: 10
+                },
+                {
+                    messageId: "restricted",
+                    data: { name: "undefined" },
+                    type: "Identifier",
+                    column: 28
+                }
+            ]
+        },
+        {
+            code: "class Number {} x = Number.NaN;",
+            options: ["Number"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "Number" },
+                    type: "Identifier",
+                    column: 7
+                },
+                {
+                    messageId: "restricted",
+                    data: { name: "Number" },
+                    type: "Identifier",
+                    column: 21
+                }
+            ]
+        },
+
+        /*
+         * Assignment to a property with a restricted name isn't allowed, in general.
+         * In this case, that restriction prevents creating a global variable with a restricted name.
+         */
+        {
+            code: "/* globals myGlobal */ window.myGlobal = 5; foo = myGlobal;",
+            options: ["myGlobal"],
+            env: { browser: true },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "myGlobal" },
+                    type: "Identifier",
+                    column: 31
+                }
+            ]
+        },
+
+        // disabled global variables
+        {
+            code: "var foo = undefined;",
+            options: ["undefined"],
+            globals: { undefined: "off" },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "undefined" },
+                    type: "Identifier"
+                }
+            ]
+        },
+        {
+            code: "/* globals Number: off */ Number.parseInt()",
+            options: ["Number"],
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "Number" },
+                    type: "Identifier"
+                }
+            ]
+        },
+        {
+            code: "var foo = [Map];", // this actually isn't a disabled global: it was never enabled because es6 environment isn't enabled
+            options: ["Map"],
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "Map" },
+                    type: "Identifier"
+                }
+            ]
+        },
+
+        // shadowed global variables
+        {
+            code: "if (foo) { let undefined; bar = undefined; }",
+            options: ["undefined"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "undefined" },
+                    type: "Identifier",
+                    column: 16
+                },
+                {
+                    messageId: "restricted",
+                    data: { name: "undefined" },
+                    type: "Identifier",
+                    column: 33
+                }
+            ]
+        },
+        {
+            code: "function foo(Number) { var x = Number.NaN; }",
+            options: ["Number"],
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "Number" },
+                    type: "Identifier",
+                    column: 14
+                },
+                {
+                    messageId: "restricted",
+                    data: { name: "Number" },
+                    type: "Identifier",
+                    column: 32
+                }
+            ]
+        },
+        {
+            code: "function foo() { var myGlobal; x = myGlobal; }",
+            options: ["myGlobal"],
+            globals: { myGlobal: "readonly" },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "myGlobal" },
+                    type: "Identifier",
+                    column: 22
+                },
+                {
+                    messageId: "restricted",
+                    data: { name: "myGlobal" },
+                    type: "Identifier",
+                    column: 36
+                }
+            ]
+        },
+        {
+            code: "function foo(bar) { return Number.parseInt(bar); } const Number = 1;",
+            options: ["Number"],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "Number" },
+                    type: "Identifier",
+                    column: 28
+                },
+                {
+                    messageId: "restricted",
+                    data: { name: "Number" },
+                    type: "Identifier",
+                    column: 58
+                }
+            ]
+        },
+        {
+            code: "import Number from 'myNumber'; const foo = Number.parseInt(bar);",
+            options: ["Number"],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "Number" },
+                    type: "Identifier",
+                    column: 8
+                },
+                {
+                    messageId: "restricted",
+                    data: { name: "Number" },
+                    type: "Identifier",
+                    column: 44
+                }
+            ]
+        },
+        {
+            code: "var foo = function undefined() {};",
+            options: ["undefined"],
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "undefined" },
+                    type: "Identifier"
+                }
+            ]
+        },
+
+        // this is a reference to a global variable, but at the same time creates a property with a restricted name
+        {
+            code: "var foo = { undefined }",
+            options: ["undefined"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "restricted",
+                    data: { name: "undefined" },
+                    type: "Identifier"
+                }
+            ]
+        }
+    ]
+});
index a63ae1be99c9cd32db6015548101d23d3e8c42f6..99a3957939c453cbf913fa416e3ad341255f1410 100644 (file)
@@ -77,7 +77,12 @@ ruleTester.run("id-length", rule, {
         { code: "var {x} = foo;", options: [{ properties: "never" }], parserOptions: { ecmaVersion: 6 } },
         { code: "var {x, y: {z}} = foo;", options: [{ properties: "never" }], parserOptions: { ecmaVersion: 6 } },
         { code: "let foo = { [a]: 1 };", options: [{ properties: "always" }], parserOptions: { ecmaVersion: 6 } },
-        { code: "let foo = { [a + b]: 1 };", options: [{ properties: "always" }], parserOptions: { ecmaVersion: 6 } }
+        { code: "let foo = { [a + b]: 1 };", options: [{ properties: "always" }], parserOptions: { ecmaVersion: 6 } },
+        { code: "function BEFORE_send() {};", options: [{ min: 3, max: 5, exceptionPatterns: ["^BEFORE_"] }] },
+        { code: "function BEFORE_send() {};", options: [{ min: 3, max: 5, exceptionPatterns: ["^BEFORE_", "send$"] }] },
+        { code: "function BEFORE_send() {};", options: [{ min: 3, max: 5, exceptionPatterns: ["^BEFORE_", "^A", "^Z"] }] },
+        { code: "function BEFORE_send() {};", options: [{ min: 3, max: 5, exceptionPatterns: ["^A", "^BEFORE_", "^Z"] }] },
+        { code: "var x = 1 ;", options: [{ min: 3, max: 5, exceptionPatterns: ["[x-z]"] }] }
     ],
     invalid: [
         { code: "var x = 1;", errors: [tooShortError] },
@@ -107,6 +112,13 @@ ruleTester.run("id-length", rule, {
                 tooLongError
             ]
         },
+        {
+            code: "var toString;",
+            options: [{ max: 5 }],
+            errors: [
+                tooLongError
+            ]
+        },
         {
             code: "(a) => { a * a };",
             parserOptions: { ecmaVersion: 6 },
@@ -197,6 +209,19 @@ ruleTester.run("id-length", rule, {
                 }
             ]
         },
+        {
+            code: "var hasOwnProperty;",
+            options: [{ max: 10, exceptions: [] }],
+            errors: [
+                {
+                    messageId: "tooLong",
+                    data: { name: "hasOwnProperty", max: 10 },
+                    line: 1,
+                    column: 5,
+                    type: "Identifier"
+                }
+            ]
+        },
         {
             code: "function foo({ a: { b: { c: d, e } } }) { }",
             parserOptions: { ecmaVersion: 6 },
@@ -440,6 +465,27 @@ ruleTester.run("id-length", rule, {
             errors: [
                 tooShortError
             ]
+        },
+        {
+            code: "function BEFORE_send() {};",
+            options: [{ min: 3, max: 5 }],
+            errors: [
+                tooLongError
+            ]
+        },
+        {
+            code: "function NOTMATCHED_send() {};",
+            options: [{ min: 3, max: 5, exceptionPatterns: ["^BEFORE_"] }],
+            errors: [
+                tooLongError
+            ]
+        },
+        {
+            code: "function N() {};",
+            options: [{ min: 3, max: 5, exceptionPatterns: ["^BEFORE_"] }],
+            errors: [
+                tooShortError
+            ]
         }
     ]
 });
index 56589be51693c3abc04c67bed52f9f1de3669452..ad35ad09c70d8b175a726ba787ff2b23df46953e 100644 (file)
@@ -5724,6 +5724,105 @@ ruleTester.run("indent", rule, {
             `,
             options: [4, { MemberExpression: 2 }],
             parserOptions: { ecmaVersion: 2015 }
+        },
+        {
+            code: unIndent`
+                const foo = async (arg1,
+                                   arg2) =>
+                {
+                  return arg1 + arg2;
+                }
+            `,
+            options: [2, { FunctionDeclaration: { parameters: "first" }, FunctionExpression: { parameters: "first" } }]
+        },
+        {
+            code: unIndent`
+                const foo = async /* some comments */(arg1,
+                                                      arg2) =>
+                {
+                  return arg1 + arg2;
+                }
+            `,
+            options: [2, { FunctionDeclaration: { parameters: "first" }, FunctionExpression: { parameters: "first" } }]
+        },
+        {
+            code: unIndent`
+                const a = async
+                b => {}
+            `,
+            options: [2]
+        },
+        {
+            code: unIndent`
+                const foo = (arg1,
+                             arg2) => async (arr1,
+                                             arr2) =>
+                {
+                  return arg1 + arg2;
+                }
+            `,
+            options: [2, { FunctionDeclaration: { parameters: "first" }, FunctionExpression: { parameters: "first" } }]
+        },
+        {
+            code: unIndent`
+                const foo = async (arg1,
+                  arg2) =>
+                {
+                  return arg1 + arg2;
+                }
+            `,
+            options: [2]
+        },
+        {
+            code: unIndent`
+                const foo = async /*comments*/(arg1,
+                  arg2) =>
+                {
+                  return arg1 + arg2;
+                }
+            `,
+            options: [2]
+        },
+        {
+            code: unIndent`
+                const foo = async (arg1,
+                        arg2) =>
+                {
+                  return arg1 + arg2;
+                }
+            `,
+            options: [2, { FunctionDeclaration: { parameters: 4 }, FunctionExpression: { parameters: 4 } }]
+        },
+        {
+            code: unIndent`
+                const foo = (arg1,
+                        arg2) =>
+                {
+                  return arg1 + arg2;
+                }
+            `,
+            options: [2, { FunctionDeclaration: { parameters: 4 }, FunctionExpression: { parameters: 4 } }]
+        },
+        {
+            code: unIndent`
+                async function fn(ar1,
+                                  ar2){}
+            `,
+            options: [2, { FunctionDeclaration: { parameters: "first" }, FunctionExpression: { parameters: "first" } }]
+        },
+        {
+            code: unIndent`
+                async function /* some comments */ fn(ar1,
+                                                      ar2){}
+            `,
+            options: [2, { FunctionDeclaration: { parameters: "first" }, FunctionExpression: { parameters: "first" } }]
+        },
+        {
+            code: unIndent`
+                async  /* some comments */  function fn(ar1,
+                                                        ar2){}
+            `,
+            options: [2, { FunctionDeclaration: { parameters: "first" }, FunctionExpression: { parameters: "first" } }]
         }
     ],
 
@@ -11374,6 +11473,140 @@ ruleTester.run("indent", rule, {
                 [5, 4, 0, "Identifier"],
                 [6, 0, 4, "Punctuator"]
             ])
+        },
+
+        // Optional chaining
+        {
+            code: unIndent`
+                obj
+                ?.prop
+                ?.[key]
+                ?.
+                [key]
+            `,
+            output: unIndent`
+                obj
+                    ?.prop
+                    ?.[key]
+                    ?.
+                        [key]
+            `,
+            options: [4],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: expectedErrors([
+                [2, 4, 0, "Punctuator"],
+                [3, 4, 0, "Punctuator"],
+                [4, 4, 0, "Punctuator"],
+                [5, 8, 0, "Punctuator"]
+            ])
+        },
+        {
+            code: unIndent`
+                (
+                    longSomething
+                        ?.prop
+                        ?.[key]
+                )
+                ?.prop
+                ?.[key]
+            `,
+            output: unIndent`
+                (
+                    longSomething
+                        ?.prop
+                        ?.[key]
+                )
+                    ?.prop
+                    ?.[key]
+            `,
+            options: [4],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: expectedErrors([
+                [6, 4, 0, "Punctuator"],
+                [7, 4, 0, "Punctuator"]
+            ])
+        },
+        {
+            code: unIndent`
+                obj
+                ?.(arg)
+                ?.
+                (arg)
+            `,
+            output: unIndent`
+                obj
+                    ?.(arg)
+                    ?.
+                    (arg)
+            `,
+            options: [4],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: expectedErrors([
+                [2, 4, 0, "Punctuator"],
+                [3, 4, 0, "Punctuator"],
+                [4, 4, 0, "Punctuator"]
+            ])
+        },
+        {
+            code: unIndent`
+                (
+                    longSomething
+                        ?.(arg)
+                        ?.(arg)
+                )
+                ?.(arg)
+                ?.(arg)
+            `,
+            output: unIndent`
+                (
+                    longSomething
+                        ?.(arg)
+                        ?.(arg)
+                )
+                    ?.(arg)
+                    ?.(arg)
+            `,
+            options: [4],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: expectedErrors([
+                [6, 4, 0, "Punctuator"],
+                [7, 4, 0, "Punctuator"]
+            ])
+        },
+        {
+            code: unIndent`
+                const foo = async (arg1,
+                                    arg2) =>
+                {
+                  return arg1 + arg2;
+                }
+            `,
+            output: unIndent`
+                const foo = async (arg1,
+                                   arg2) =>
+                {
+                  return arg1 + arg2;
+                }
+            `,
+            options: [2, { FunctionDeclaration: { parameters: "first" }, FunctionExpression: { parameters: "first" } }],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: expectedErrors([
+                [2, 19, 20, "Identifier"]
+            ])
+        },
+        {
+            code: unIndent`
+                const a = async
+                 b => {}
+            `,
+            output: unIndent`
+                const a = async
+                b => {}
+            `,
+            options: [2],
+            errors: expectedErrors([
+                [2, 0, 1, "Identifier"]
+            ])
         }
     ]
 });
index 29bbbe8578166609822d3dcea9aad066c079a55f..849ce61994976e90b8e0574f43f3ee892637b7bc 100644 (file)
@@ -892,16 +892,110 @@ ruleTester.run("key-spacing", rule, {
                 on: "value"
             }
         }]
-    }],
-
+    }
+    ],
     invalid: [{
+        code: "var a ={'key' : value };",
+        output: "var a ={'key':value };",
+        options: [{
+            beforeColon: false,
+            afterColon: false
+        }],
+        errors: [
+            {
+
+                type: "Literal",
+                messageId: "extraKey",
+                data: { computed: "", key: "key" },
+                line: 1,
+                column: 14,
+                endLine: 1,
+                endColumn: 15
+            },
+            {
+                type: "Identifier",
+                messageId: "extraValue",
+                data: { computed: "", key: "key" },
+                line: 1,
+                column: 15,
+                endLine: 1,
+                endColumn: 17
+            }
+        ]
+    }, {
+        code: "var a ={'key' :value };",
+        output: "var a ={'key': value };",
+        options: [{
+            beforeColon: false,
+            afterColon: true
+        }],
+        errors: [
+            {
+
+                type: "Literal",
+                messageId: "extraKey",
+                data: { computed: "", key: "key" },
+                line: 1,
+                column: 14,
+                endLine: 1,
+                endColumn: 15
+            },
+            {
+                type: "Identifier",
+                messageId: "missingValue",
+                data: { computed: "", key: "key" },
+                line: 1,
+                column: 16,
+                endLine: 1,
+                endColumn: 21
+            }
+        ]
+    }, {
+        code: "var a ={'key'\n : \nvalue };",
+        output: "var a ={'key':value };",
+        options: [{
+            beforeColon: false,
+            afterColon: false
+        }],
+        errors: [
+            {
+
+                type: "Literal",
+                messageId: "extraKey",
+                data: { computed: "", key: "key" },
+                line: 1,
+                column: 14,
+                endLine: 2,
+                endColumn: 2
+            },
+            {
+                type: "Identifier",
+                messageId: "extraValue",
+                data: { computed: "", key: "key" },
+                line: 2,
+                column: 2,
+                endLine: 3,
+                endColumn: 1
+            }
+        ]
+    }, {
         code: "var bat = function() { return { foo:bar, 'key': value }; };",
         output: "var bat = function() { return { foo:bar, 'key':value }; };",
         options: [{
             beforeColon: false,
             afterColon: false
         }],
-        errors: [{ messageId: "extraValue", data: { computed: "", key: "key" }, type: "Identifier", line: 1, column: 49 }]
+        errors: [
+            {
+                messageId: "extraValue",
+                data: { computed: "", key: "key" },
+                type: "Identifier",
+                line: 1,
+                column: 47,
+                endLine: 1,
+                endColumn: 49
+            }
+        ]
     }, {
         code: "var obj = { [ (a + b) ]:value };",
         output: "var obj = { [ (a + b) ]: value };",
@@ -915,7 +1009,7 @@ ruleTester.run("key-spacing", rule, {
             beforeColon: false,
             afterColon: false
         }],
-        errors: [{ messageId: "extraKey", data: { computed: "", key: "key" }, type: "Literal", line: 1, column: 15 }]
+        errors: [{ messageId: "extraKey", data: { computed: "", key: "key" }, type: "Literal", line: 1, column: 20, endLine: 1, endColumn: 21 }]
     }, {
         code: "var obj = {prop :(42)};",
         output: "var obj = {prop : (42)};",
@@ -940,10 +1034,10 @@ ruleTester.run("key-spacing", rule, {
             afterColon: true
         }],
         errors: [
-            { messageId: "extraKey", data: { computed: "", key: "a" }, type: "Literal", line: 1, column: 3 },
-            { messageId: "missingValue", data: { computed: "", key: "a" }, type: "CallExpression", line: 1, column: 9 },
-            { messageId: "missingKey", data: { computed: "", key: "b" }, type: "Identifier", line: 1, column: 16 },
-            { messageId: "extraValue", data: { computed: "", key: "b" }, type: "CallExpression", line: 1, column: 20 }
+            { messageId: "extraKey", data: { computed: "", key: "a" }, type: "Literal", line: 1, column: 6, endLine: 1, endColumn: 8 },
+            { messageId: "missingValue", data: { computed: "", key: "a" }, type: "CallExpression", line: 1, column: 9, endLine: 1, endColumn: 12 },
+            { messageId: "missingKey", data: { computed: "", key: "b" }, type: "Identifier", line: 1, column: 16, endLine: 1, endColumn: 17 },
+            { messageId: "extraValue", data: { computed: "", key: "b" }, type: "CallExpression", line: 1, column: 17, endLine: 1, endColumn: 20 }
         ]
     }, {
         code: "bar = { key:value };",
@@ -973,7 +1067,7 @@ ruleTester.run("key-spacing", rule, {
         }],
         errors: [
             { messageId: "missingKey", data: { computed: "", key: "key" }, type: "Identifier", line: 2, column: 5 },
-            { messageId: "extraValue", data: { computed: "", key: "key" }, type: "Identifier", line: 2, column: 12 },
+            { messageId: "extraValue", data: { computed: "", key: "key" }, type: "Identifier", line: 2, column: 8 },
             { messageId: "missingValue", data: { computed: "", key: "foobar" }, type: "CallExpression", line: 3, column: 12 }
         ]
     }, {
@@ -999,9 +1093,9 @@ ruleTester.run("key-spacing", rule, {
             afterColon: false
         }],
         errors: [
-            { messageId: "extraValue", data: { computed: "", key: "a" }, type: "Identifier", line: 2, column: 11 },
+            { messageId: "extraValue", data: { computed: "", key: "a" }, type: "Identifier", line: 2, column: 9 },
             { messageId: "missingKey", data: { computed: "", key: "foo" }, type: "Identifier", line: 3, column: 5 },
-            { messageId: "extraKey", data: { computed: "", key: "b" }, type: "Identifier", line: 4, column: 5 }
+            { messageId: "extraKey", data: { computed: "", key: "b" }, type: "Identifier", line: 4, column: 6 }
         ]
     }, {
         code: [
@@ -1027,10 +1121,10 @@ ruleTester.run("key-spacing", rule, {
         }],
         parserOptions: { ecmaVersion: 6 },
         errors: [
-            { messageId: "extraValue", data: { computed: "", key: "a" }, type: "CallExpression", line: 2, column: 11 },
-            { messageId: "extraKey", data: { computed: "", key: "b" }, type: "Literal", line: 3, column: 5 },
+            { messageId: "extraValue", data: { computed: "", key: "a" }, type: "CallExpression", line: 2, column: 6 },
+            { messageId: "extraKey", data: { computed: "", key: "b" }, type: "Literal", line: 3, column: 8 },
             { messageId: "missingValue", data: { computed: "", key: "foo" }, type: "Identifier", line: 4, column: 9 },
-            { messageId: "extraKey", data: { computed: "computed ", key: "a" }, type: "Identifier", line: 6, column: 7 }
+            { messageId: "extraKey", data: { computed: "computed ", key: "a" }, type: "Identifier", line: 6, column: 8 }
         ]
     }, {
         code: [
@@ -1056,7 +1150,7 @@ ruleTester.run("key-spacing", rule, {
         }],
         errors: [
             { messageId: "missingKey", data: { computed: "", key: "a" }, type: "Identifier", line: 2, column: 5 },
-            { messageId: "extraValue", data: { computed: "", key: "bar" }, type: "CallExpression", line: 5, column: 11 }
+            { messageId: "extraValue", data: { computed: "", key: "bar" }, type: "CallExpression", line: 5, column: 9 }
         ]
     }, {
         code: [
@@ -1105,8 +1199,8 @@ ruleTester.run("key-spacing", rule, {
             afterColon: false
         }],
         errors: [
-            { messageId: "extraValue", data: { computed: "", key: "key" }, type: "Identifier", line: 3, column: 9 },
-            { messageId: "extraKey", data: { computed: "", key: "key2" }, type: "Identifier", line: 4, column: 5 }
+            { messageId: "extraValue", data: { computed: "", key: "key" }, type: "Identifier", line: 2, column: 8 },
+            { messageId: "extraKey", data: { computed: "", key: "key2" }, type: "Identifier", line: 4, column: 9 }
         ]
     }, {
         code: [
@@ -1153,7 +1247,7 @@ ruleTester.run("key-spacing", rule, {
         output: "var obj = {a: 'foo', bar: 'bam'};",
         options: [{ align: "colon" }],
         errors: [
-            { messageId: "extraKey", data: { computed: "", key: "a" }, line: 1, column: 12, type: "Identifier" }
+            { messageId: "extraKey", data: { computed: "", key: "a" }, line: 1, column: 13, type: "Identifier" }
         ]
     }, {
         code: [
@@ -1170,7 +1264,7 @@ ruleTester.run("key-spacing", rule, {
         ].join("\n"),
         options: [{ align: "colon" }],
         errors: [
-            { messageId: "extraKey", data: { computed: "", key: "b" }, line: 3, column: 5, type: "Identifier" }
+            { messageId: "extraKey", data: { computed: "", key: "b" }, line: 3, column: 6, type: "Identifier" }
         ]
     }, {
         code: [
@@ -1203,7 +1297,7 @@ ruleTester.run("key-spacing", rule, {
             afterColon: true
         }],
         errors: [
-            { messageId: "extraValue", data: { computed: "", key: "key" }, line: 2, column: 8, type: "Identifier" }
+            { messageId: "extraValue", data: { computed: "", key: "key" }, line: 2, column: 2, type: "Identifier" }
         ]
     }, {
         code: [
@@ -1243,7 +1337,7 @@ ruleTester.run("key-spacing", rule, {
         options: [{ align: "value" }],
         parserOptions: { ecmaVersion: 6 },
         errors: [
-            { messageId: "extraValue", data: { computed: "", key: "foobar" }, line: 2, column: 14, type: "Literal" }
+            { messageId: "extraValue", data: { computed: "", key: "foobar" }, line: 2, column: 11, type: "Literal" }
         ]
     }, {
         code: [
@@ -1283,7 +1377,7 @@ ruleTester.run("key-spacing", rule, {
         options: [{ align: "value" }],
         parserOptions: { ecmaVersion: 6 },
         errors: [
-            { messageId: "extraValue", data: { computed: "", key: "foobar" }, line: 2, column: 14, type: "Literal" }
+            { messageId: "extraValue", data: { computed: "", key: "foobar" }, line: 2, column: 11, type: "Literal" }
         ]
     }, {
         code: [
@@ -1307,7 +1401,7 @@ ruleTester.run("key-spacing", rule, {
         options: [{ align: "value" }],
         parserOptions: { ecmaVersion: 6 },
         errors: [
-            { messageId: "extraValue", data: { computed: "", key: "baz" }, line: 6, column: 13, type: "Literal" }
+            { messageId: "extraValue", data: { computed: "", key: "baz" }, line: 6, column: 8, type: "Literal" }
         ]
     }, {
         code: [
@@ -1341,7 +1435,7 @@ ruleTester.run("key-spacing", rule, {
         ].join("\n"),
         options: [{ align: "colon" }],
         errors: [
-            { messageId: "extraValue", data: { computed: "", key: "cats" }, line: 3, column: 12, type: "Identifier" }
+            { messageId: "extraValue", data: { computed: "", key: "cats" }, line: 3, column: 9, type: "Identifier" }
         ]
     }, {
         code: [
@@ -1371,7 +1465,7 @@ ruleTester.run("key-spacing", rule, {
         ].join("\n"),
         options: [{ align: "colon" }],
         errors: [
-            { messageId: "extraKey", data: { computed: "", key: "foo" }, line: 1, column: 13, type: "Identifier" }
+            { messageId: "extraKey", data: { computed: "", key: "foo" }, line: 1, column: 16, type: "Identifier" }
         ]
     }, {
         code: [
@@ -1401,7 +1495,7 @@ ruleTester.run("key-spacing", rule, {
         ].join("\n"),
         options: [{ align: "colon" }],
         errors: [
-            { messageId: "extraValue", data: { computed: "", key: "foo" }, line: 1, column: 20, type: "Identifier" }
+            { messageId: "extraValue", data: { computed: "", key: "foo" }, line: 1, column: 17, type: "Identifier" }
         ]
     }, {
         code: [
@@ -1416,7 +1510,7 @@ ruleTester.run("key-spacing", rule, {
         ].join("\n"),
         options: [{ align: "colon" }],
         errors: [
-            { messageId: "extraValue", data: { computed: "", key: "cats" }, line: 2, column: 20, type: "Identifier" }
+            { messageId: "extraValue", data: { computed: "", key: "cats" }, line: 2, column: 17, type: "Identifier" }
         ]
     },
 
@@ -1456,7 +1550,7 @@ ruleTester.run("key-spacing", rule, {
         parserOptions: { ecmaVersion: 2018 },
         errors: [
             { messageId: "missingKey", data: { computed: "", key: "a" }, line: 3, column: 5, type: "Identifier" },
-            { messageId: "extraKey", data: { computed: "", key: "f" }, line: 12, column: 5, type: "Identifier" }
+            { messageId: "extraKey", data: { computed: "", key: "f" }, line: 12, column: 6, type: "Identifier" }
         ]
     },
 
@@ -1478,7 +1572,7 @@ ruleTester.run("key-spacing", rule, {
             align: "colon"
         }],
         errors: [
-            { messageId: "extraKey", data: { computed: "", key: "a" }, line: 2, column: 5, type: "Identifier" }
+            { messageId: "extraKey", data: { computed: "", key: "a" }, line: 2, column: 6, type: "Identifier" }
         ]
     }, {
         code: [
@@ -1497,7 +1591,7 @@ ruleTester.run("key-spacing", rule, {
             align: "value"
         }],
         errors: [
-            { messageId: "extraKey", data: { computed: "", key: "c" }, line: 3, column: 5, type: "Identifier" }
+            { messageId: "extraKey", data: { computed: "", key: "c" }, line: 3, column: 6, type: "Identifier" }
         ]
     }, {
         code: [
@@ -1554,7 +1648,7 @@ ruleTester.run("key-spacing", rule, {
             }
         }],
         errors: [
-            { messageId: "extraValue", data: { computed: "", key: "a1" }, line: 6, column: 17, type: "Literal" }
+            { messageId: "extraValue", data: { computed: "", key: "a1" }, line: 6, column: 15, type: "Literal" }
         ]
     }, {
         code: [
@@ -1635,7 +1729,7 @@ ruleTester.run("key-spacing", rule, {
             mode: "minimum"
         }],
         errors: [
-            { messageId: "extraKey", data: { computed: "", key: "ex" }, line: 4, column: 4, type: "Identifier" }
+            { messageId: "extraKey", data: { computed: "", key: "ex" }, line: 4, column: 6, type: "Identifier" }
         ]
     }, {
         code: [
@@ -1674,7 +1768,7 @@ ruleTester.run("key-spacing", rule, {
         parserOptions: { ecmaVersion: 2018 },
         errors: [
             { messageId: "missingValue", data: { computed: "", key: "a" }, line: 1, column: 6, type: "Identifier" },
-            { messageId: "extraKey", data: { computed: "", key: "c" }, line: 1, column: 20, type: "Identifier" }
+            { messageId: "extraKey", data: { computed: "", key: "c" }, line: 1, column: 21, type: "Identifier" }
         ]
     },
 
@@ -1755,8 +1849,8 @@ ruleTester.run("key-spacing", rule, {
             { messageId: "missingValue", data: { computed: "", key: "func" }, line: 2, column: 10, type: "FunctionExpression" },
             { messageId: "missingKey", data: { computed: "", key: "longName" }, line: 5, column: 5, type: "Identifier" },
             { messageId: "missingKey", data: { computed: "", key: "small" }, line: 6, column: 5, type: "Identifier" },
-            { messageId: "extraKey", data: { computed: "", key: "xs" }, line: 7, column: 5, type: "Identifier" },
-            { messageId: "extraKey", data: { computed: "", key: "singleLine" }, line: 11, column: 5, type: "Identifier" }
+            { messageId: "extraKey", data: { computed: "", key: "xs" }, line: 7, column: 7, type: "Identifier" },
+            { messageId: "extraKey", data: { computed: "", key: "singleLine" }, line: 11, column: 15, type: "Identifier" }
         ]
     }, {
         code: [
@@ -1803,10 +1897,10 @@ ruleTester.run("key-spacing", rule, {
         errors: [
             { messageId: "missingValue", data: { computed: "", key: "func" }, line: 2, column: 10, type: "FunctionExpression" },
             { messageId: "missingKey", data: { computed: "", key: "small" }, line: 6, column: 5, type: "Identifier" },
-            { messageId: "extraKey", data: { computed: "", key: "xs" }, line: 7, column: 5, type: "Identifier" },
-            { messageId: "extraValue", data: { computed: "", key: "xs" }, line: 7, column: 21, type: "Literal" },
-            { messageId: "extraValue", data: { computed: "", key: "func2" }, line: 8, column: 16, type: "FunctionExpression" },
-            { messageId: "extraKey", data: { computed: "", key: "singleLine" }, line: 11, column: 5, type: "Identifier" }
+            { messageId: "extraKey", data: { computed: "", key: "xs" }, line: 7, column: 7, type: "Identifier" },
+            { messageId: "extraValue", data: { computed: "", key: "xs" }, line: 7, column: 19, type: "Literal" },
+            { messageId: "extraValue", data: { computed: "", key: "func2" }, line: 8, column: 14, type: "FunctionExpression" },
+            { messageId: "extraKey", data: { computed: "", key: "singleLine" }, line: 11, column: 15, type: "Identifier" }
         ]
     }, {
         code: [
@@ -1843,8 +1937,8 @@ ruleTester.run("key-spacing", rule, {
         }],
         parserOptions: { ecmaVersion: 6 },
         errors: [
-            { messageId: "extraValue", data: { computed: "", key: "key2" }, line: 4, column: 14, type: "Literal" },
-            { messageId: "extraValue", data: { computed: "", key: "key3" }, line: 5, column: 14, type: "Literal" }
+            { messageId: "extraValue", data: { computed: "", key: "key2" }, line: 4, column: 9, type: "Literal" },
+            { messageId: "extraValue", data: { computed: "", key: "key3" }, line: 5, column: 9, type: "Literal" }
         ]
     }, {
         code: [
@@ -1881,20 +1975,20 @@ ruleTester.run("key-spacing", rule, {
         }],
         parserOptions: { ecmaVersion: 6 },
         errors: [
-            { messageId: "extraValue", data: { computed: "", key: "key2" }, line: 4, column: 14, type: "Literal" },
-            { messageId: "extraValue", data: { computed: "", key: "key3" }, line: 5, column: 14, type: "Literal" }
+            { messageId: "extraValue", data: { computed: "", key: "key2" }, line: 4, column: 9, type: "Literal" },
+            { messageId: "extraValue", data: { computed: "", key: "key3" }, line: 5, column: 9, type: "Literal" }
         ]
     }, {
 
         // https://github.com/eslint/eslint/issues/7603
         code: "({ foo/* comment */ : bar })",
         output: "({ foo/* comment */: bar })",
-        errors: [{ messageId: "extraKey", data: { computed: "", key: "foo" }, line: 1, column: 7, type: "Identifier" }]
+        errors: [{ messageId: "extraKey", data: { computed: "", key: "foo" }, line: 1, column: 20, type: "Identifier" }]
     }, {
         code: "({ foo: /* comment */bar })",
         output: "({ foo:/* comment */bar })",
         options: [{ afterColon: false }],
-        errors: [{ messageId: "extraValue", data: { computed: "", key: "foo" }, line: 1, column: 9, type: "Identifier" }]
+        errors: [{ messageId: "extraValue", data: { computed: "", key: "foo" }, line: 1, column: 7, type: "Identifier" }]
     },
     {
         code: "({ foo/*comment*/:/*comment*/bar })",
@@ -1951,10 +2045,10 @@ ruleTester.run("key-spacing", rule, {
             }
         }],
         errors: [
-            { messageId: "extraKey", data: { computed: "", key: "foo" }, line: 2, column: 5, type: "Identifier" },
-            { messageId: "extraKey", data: { computed: "", key: "bar" }, line: 2, column: 14, type: "Literal" },
-            { messageId: "extraKey", data: { computed: "", key: "baz" }, line: 2, column: 25, type: "Identifier" },
-            { messageId: "extraKey", data: { computed: "", key: "longlonglong" }, line: 2, column: 34, type: "Identifier" }
+            { messageId: "extraKey", data: { computed: "", key: "foo" }, line: 2, column: 8, type: "Identifier" },
+            { messageId: "extraKey", data: { computed: "", key: "bar" }, line: 2, column: 19, type: "Literal" },
+            { messageId: "extraKey", data: { computed: "", key: "baz" }, line: 2, column: 28, type: "Identifier" },
+            { messageId: "extraKey", data: { computed: "", key: "longlonglong" }, line: 2, column: 46, type: "Identifier" }
         ]
     }, {
         code: [
@@ -2015,10 +2109,10 @@ ruleTester.run("key-spacing", rule, {
             }
         }],
         errors: [
-            { messageId: "extraValue", data: { computed: "", key: "foo" }, line: 1, column: 18, type: "Literal" },
-            { messageId: "extraValue", data: { computed: "", key: "bar" }, line: 1, column: 28, type: "Literal" },
-            { messageId: "extraKey", data: { computed: "", key: "baz" }, line: 1, column: 31, type: "Identifier" },
-            { messageId: "extraKey", data: { computed: "", key: "longlonglong" }, line: 1, column: 39, type: "Identifier" }
+            { messageId: "extraValue", data: { computed: "", key: "foo" }, line: 1, column: 16, type: "Literal" },
+            { messageId: "extraValue", data: { computed: "", key: "bar" }, line: 1, column: 26, type: "Literal" },
+            { messageId: "extraKey", data: { computed: "", key: "baz" }, line: 1, column: 34, type: "Identifier" },
+            { messageId: "extraKey", data: { computed: "", key: "longlonglong" }, line: 1, column: 51, type: "Identifier" }
         ]
     }, {
         code: [
index b67ee52384b893c0b56f343b512860eadfcd267d..64ee5a434742edd0837d4e8619785776b9b7ca31 100644 (file)
@@ -1364,14 +1364,14 @@ ruleTester.run("keyword-spacing", rule, {
             code: "import *as a from \"foo\"",
             output: "import * as a from \"foo\"",
             parserOptions: { ecmaVersion: 6, sourceType: "module" },
-            errors: expectedBefore("as")
-        },
-        {
-            code: "import* as a from\"foo\"",
-            output: "import*as a from\"foo\"",
-            options: [NEITHER],
-            parserOptions: { ecmaVersion: 6, sourceType: "module" },
-            errors: unexpectedBefore("as")
+            errors: [{
+                messageId: "expectedBefore",
+                data: { value: "as" },
+                line: 1,
+                column: 9,
+                endLine: 1,
+                endColumn: 11
+            }]
         },
         {
             code: "import* as a from\"foo\"",
@@ -1380,27 +1380,26 @@ ruleTester.run("keyword-spacing", rule, {
             parserOptions: { ecmaVersion: 6, sourceType: "module" },
             errors: [{
                 messageId: "unexpectedBefore",
+                data: { value: "as" },
                 line: 1,
                 column: 8,
                 endLine: 1,
                 endColumn: 9
-            }
-            ]
+            }]
         },
         {
-            code: "import *as a from\"foo\"",
+            code: "import*   as a from\"foo\"",
             output: "import*as a from\"foo\"",
             options: [NEITHER],
             parserOptions: { ecmaVersion: 6, sourceType: "module" },
-            errors: [
-                {
-                    messageId: "unexpectedAfter",
-                    line: 1,
-                    column: 7,
-                    endLine: 1,
-                    endColumn: 8
-                }
-            ]
+            errors: [{
+                messageId: "unexpectedBefore",
+                data: { value: "as" },
+                line: 1,
+                column: 8,
+                endLine: 1,
+                endColumn: 11
+            }]
         },
         {
             code: "import*as a from\"foo\"",
@@ -2510,6 +2509,47 @@ ruleTester.run("keyword-spacing", rule, {
         // import
         //----------------------------------------------------------------------
 
+        {
+            code: "import* as a from \"foo\"",
+            output: "import * as a from \"foo\"",
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [{
+                messageId: "expectedAfter",
+                data: { value: "import" },
+                line: 1,
+                column: 1,
+                endLine: 1,
+                endColumn: 7
+            }]
+        },
+        {
+            code: "import *as a from\"foo\"",
+            output: "import*as a from\"foo\"",
+            options: [NEITHER],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [{
+                messageId: "unexpectedAfter",
+                data: { value: "import" },
+                line: 1,
+                column: 7,
+                endLine: 1,
+                endColumn: 8
+            }]
+        },
+        {
+            code: "import   *as a from\"foo\"",
+            output: "import*as a from\"foo\"",
+            options: [NEITHER],
+            parserOptions: { ecmaVersion: 6, sourceType: "module" },
+            errors: [{
+                messageId: "unexpectedAfter",
+                data: { value: "import" },
+                line: 1,
+                column: 7,
+                endLine: 1,
+                endColumn: 10
+            }]
+        },
         {
             code: "{}import{a} from \"foo\"",
             output: "{} import {a} from \"foo\"",
index f6296b636b91c083fb09af1e92272748af2c0e7f..557b5506f8928187604b5a6fdf9db8d187282a2b 100644 (file)
@@ -341,7 +341,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 86, maxLength: 80 },
                     type: "Program",
                     line: 1,
-                    column: 1
+                    column: 1,
+                    endLine: 1,
+                    endColumn: 30
                 }
             ]
         },
@@ -354,7 +356,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 24, maxLength: 10 },
                     type: "Program",
                     line: 1,
-                    column: 1
+                    column: 1,
+                    endLine: 1,
+                    endColumn: 25
                 }
             ]
         },
@@ -367,7 +371,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 22, maxLength: 15 },
                     type: "Program",
                     line: 1,
-                    column: 1
+                    column: 1,
+                    endLine: 1,
+                    endColumn: 14
                 }
             ]
         },
@@ -380,14 +386,18 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 22, maxLength: 15 },
                     type: "Program",
                     line: 1,
-                    column: 1
+                    column: 1,
+                    endLine: 1,
+                    endColumn: 14
                 },
                 {
                     messageId: "max",
                     data: { lineLength: 22, maxLength: 15 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 14
                 }
             ]
         },
@@ -400,7 +410,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 56, maxLength: 20 },
                     type: "Program",
                     line: 1,
-                    column: 1
+                    column: 1,
+                    endLine: 1,
+                    endColumn: 57
                 }
             ]
         },
@@ -415,7 +427,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 54, maxLength: 20 },
                     type: "Program",
                     line: 1,
-                    column: 1
+                    column: 1,
+                    endLine: 1,
+                    endColumn: 55
                 }
             ]
         },
@@ -428,7 +442,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 30, maxLength: 10 },
                     type: "Program",
                     line: 1,
-                    column: 1
+                    column: 1,
+                    endLine: 1,
+                    endColumn: 31
                 }
             ]
         },
@@ -441,7 +457,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 62, maxLength: 40 },
                     type: "Program",
                     line: 1,
-                    column: 1
+                    column: 1,
+                    endLine: 1,
+                    endColumn: 63
                 }
             ]
         },
@@ -454,7 +472,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 57, maxLength: 40 },
                     type: "Program",
                     line: 1,
-                    column: 1
+                    column: 1,
+                    endLine: 1,
+                    endColumn: 58
                 }
             ]
         }, {
@@ -466,7 +486,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 53, maxLength: 40 },
                     type: "Program",
                     line: 1,
-                    column: 1
+                    column: 1,
+                    endLine: 1,
+                    endColumn: 54
                 }
             ]
         }, {
@@ -478,7 +500,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 49, maxCommentLength: 20 },
                     type: "Program",
                     line: 1,
-                    column: 1
+                    column: 1,
+                    endLine: 1,
+                    endColumn: 50
                 }
             ]
         }, {
@@ -490,7 +514,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 119, maxCommentLength: 80 },
                     type: "Program",
                     line: 1,
-                    column: 1
+                    column: 1,
+                    endLine: 1,
+                    endColumn: 120
                 }
             ]
         }, {
@@ -502,7 +528,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 49, maxLength: 20 },
                     type: "Program",
                     line: 1,
-                    column: 1
+                    column: 1,
+                    endLine: 1,
+                    endColumn: 50
                 }
             ]
         }, {
@@ -514,7 +542,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 73, maxLength: 40 },
                     type: "Program",
                     line: 1,
-                    column: 1
+                    column: 1,
+                    endLine: 1,
+                    endColumn: 74
                 }
             ]
         },
@@ -531,7 +561,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 29, maxCommentLength: 28 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 30
                 }
             ]
         }, {
@@ -545,7 +577,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 33, maxCommentLength: 32 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 34
                 }
             ]
         }, {
@@ -560,14 +594,18 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 29, maxCommentLength: 28 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 30
                 },
                 {
                     messageId: "maxComment",
                     data: { lineLength: 32, maxCommentLength: 28 },
                     type: "Program",
                     line: 3,
-                    column: 1
+                    column: 1,
+                    endLine: 3,
+                    endColumn: 33
                 }
             ]
         }, {
@@ -582,14 +620,18 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 33, maxCommentLength: 32 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 34
                 },
                 {
                     messageId: "maxComment",
                     data: { lineLength: 36, maxCommentLength: 32 },
                     type: "Program",
                     line: 3,
-                    column: 1
+                    column: 1,
+                    endLine: 3,
+                    endColumn: 37
                 }
             ]
         }, {
@@ -604,14 +646,18 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 40, maxLength: 39 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 41
                 },
                 {
                     messageId: "maxComment",
                     data: { lineLength: 36, maxCommentLength: 35 },
                     type: "Program",
                     line: 3,
-                    column: 1
+                    column: 1,
+                    endLine: 3,
+                    endColumn: 37
                 }
             ]
         }, {
@@ -626,14 +672,18 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 33, maxCommentLength: 32 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 34
                 },
                 {
                     messageId: "max",
                     data: { lineLength: 43, maxLength: 42 },
                     type: "Program",
                     line: 3,
-                    column: 1
+                    column: 1,
+                    endLine: 3,
+                    endColumn: 44
                 }
             ]
         },
@@ -649,7 +699,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 51, maxLength: 20 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 52
                 }
             ]
         },
@@ -664,7 +716,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 39, maxLength: 29 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 40
                 }
             ]
         },
@@ -677,7 +731,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 45, maxLength: 29 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 46
                 }
             ]
         },
@@ -690,7 +746,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 57, maxLength: 29 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 58
                 }
             ]
         },
@@ -703,7 +761,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 39, maxLength: 29 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 40
                 }
             ]
         },
@@ -717,7 +777,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 39, maxLength: 29 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 40
                 }
             ]
         },
@@ -731,14 +793,18 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 37, maxLength: 29 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 38
                 },
                 {
                     messageId: "max",
                     data: { lineLength: 44, maxLength: 29 },
                     type: "Program",
                     line: 3,
-                    column: 1
+                    column: 1,
+                    endLine: 3,
+                    endColumn: 45
                 }
             ]
         },
@@ -752,7 +818,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 58, maxLength: 29 },
                     type: "Program",
                     line: 1,
-                    column: 1
+                    column: 1,
+                    endLine: 1,
+                    endColumn: 59
                 }
             ]
         },
@@ -767,7 +835,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 12, maxLength: 10 },
                     type: "Program",
                     line: 1,
-                    column: 1
+                    column: 1,
+                    endLine: 1,
+                    endColumn: 21
                 }
             ]
         },
@@ -781,7 +851,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 1, maxLength: 0 },
                     type: "Program",
                     line: 1,
-                    column: 1
+                    column: 1,
+                    endLine: 1,
+                    endColumn: 2
                 }
             ]
         },
@@ -799,7 +871,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 38, maxCommentLength: 37 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 39
                 }
             ]
         },
@@ -815,7 +889,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 44, maxCommentLength: 40 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 39
                 }
             ]
         },
@@ -831,7 +907,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 38, maxLength: 15 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 39
                 }
             ]
         },
@@ -847,7 +925,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 38, maxLength: 37 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 39
                 }
             ]
         },
@@ -863,7 +943,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 38, maxLength: 37 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 39
                 }
             ]
         },
@@ -879,7 +961,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 50, maxLength: 49 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 51
                 }
             ]
         },
@@ -896,7 +980,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 44, maxLength: 37 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 45
                 }
             ]
         },
@@ -912,7 +998,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 57, maxLength: 56 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 58
                 }
             ]
         },
@@ -928,7 +1016,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 57, maxLength: 56 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 58
                 }
             ]
         },
@@ -944,7 +1034,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 56, maxLength: 55 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 57
                 }
             ]
         },
@@ -961,7 +1053,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 51, maxLength: 30 },
                     type: "Program",
                     line: 3,
-                    column: 1
+                    column: 1,
+                    endLine: 3,
+                    endColumn: 52
                 }
             ]
         },
@@ -978,7 +1072,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 80, maxLength: 79 },
                     type: "Program",
                     line: 3,
-                    column: 1
+                    column: 1,
+                    endLine: 3,
+                    endColumn: 81
                 }
             ]
         },
@@ -994,7 +1090,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 87, maxLength: 85 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 88
                 }
             ]
         },
@@ -1011,7 +1109,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 87, maxLength: 37 },
                     type: "Program",
                     line: 3,
-                    column: 1
+                    column: 1,
+                    endLine: 3,
+                    endColumn: 88
                 }
             ]
         },
@@ -1028,7 +1128,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 119, maxLength: 37 },
                     type: "Program",
                     line: 3,
-                    column: 1
+                    column: 1,
+                    endLine: 3,
+                    endColumn: 120
                 }
             ]
         },
@@ -1045,7 +1147,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 55, maxLength: 37 },
                     type: "Program",
                     line: 3,
-                    column: 1
+                    column: 1,
+                    endLine: 3,
+                    endColumn: 56
                 }
             ]
         },
@@ -1062,7 +1166,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 55, maxLength: 37 },
                     type: "Program",
                     line: 3,
-                    column: 1
+                    column: 1,
+                    endLine: 3,
+                    endColumn: 56
                 }
             ]
         },
@@ -1079,7 +1185,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 15, maxLength: 14 },
                     type: "Program",
                     line: 2,
-                    column: 1
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 16
                 }
             ]
         },
@@ -1096,7 +1204,9 @@ ruleTester.run("max-len", rule, {
                     data: { lineLength: 31, maxLength: 30 },
                     type: "Program",
                     line: 3,
-                    column: 1
+                    column: 1,
+                    endLine: 3,
+                    endColumn: 32
                 }
             ]
         }
index 2929551539ab4393a1b733c137952036cf649249..24c811928decc4be444b15d97f1b2dfd07e21373 100644 (file)
@@ -9,10 +9,8 @@
 //------------------------------------------------------------------------------
 
 const rule = require("../../../lib/rules/max-lines"),
-
     { RuleTester } = require("../../../lib/rule-tester");
 
-
 //------------------------------------------------------------------------------
 // Tests
 //------------------------------------------------------------------------------
@@ -23,8 +21,15 @@ ruleTester.run("max-lines", rule, {
     valid: [
         "var x;",
         "var xy;\nvar xy;",
+        { code: "A", options: [1] },
+        { code: "A\n", options: [1] },
+        { code: "A\r", options: [1] },
+        { code: "A\r\n", options: [1] },
         { code: "var xy;\nvar xy;", options: [2] },
+        { code: "var xy;\nvar xy;\n", options: [2] },
         { code: "var xy;\nvar xy;", options: [{ max: 2 }] },
+        { code: "// comment\n", options: [{ max: 0, skipComments: true }] },
+        { code: "foo;\n /* comment */\n", options: [{ max: 1, skipComments: true }] },
         {
             code: [
                 "//a single line comment",
@@ -79,17 +84,44 @@ ruleTester.run("max-lines", rule, {
         {
             code: "var xyz;\nvar xyz;\nvar xyz;",
             options: [2],
-            errors: [{ messageId: "exceed", data: { max: 2, actual: 3 } }]
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 2, actual: 3 },
+                    line: 3,
+                    column: 1,
+                    endLine: 3,
+                    endColumn: 9
+                }
+            ]
         },
         {
             code: "/* a multiline comment\n that goes to many lines*/\nvar xy;\nvar xy;",
             options: [2],
-            errors: [{ messageId: "exceed", data: { max: 2, actual: 4 } }]
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 2, actual: 4 },
+                    line: 3,
+                    column: 1,
+                    endLine: 4,
+                    endColumn: 8
+                }
+            ]
         },
         {
             code: "//a single line comment\nvar xy;\nvar xy;",
             options: [2],
-            errors: [{ messageId: "exceed", data: { max: 2, actual: 3 } }]
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 2, actual: 3 },
+                    line: 3,
+                    column: 1,
+                    endLine: 3,
+                    endColumn: 8
+                }
+            ]
         },
         {
             code: [
@@ -100,7 +132,16 @@ ruleTester.run("max-lines", rule, {
                 "var y;"
             ].join("\n"),
             options: [{ max: 2 }],
-            errors: [{ messageId: "exceed", data: { max: 2, actual: 5 } }]
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 2, actual: 5 },
+                    line: 3,
+                    column: 1,
+                    endLine: 5,
+                    endColumn: 7
+                }
+            ]
         },
         {
             code: [
@@ -114,7 +155,16 @@ ruleTester.run("max-lines", rule, {
                 " long comment*/"
             ].join("\n"),
             options: [{ max: 2, skipComments: true }],
-            errors: [{ messageId: "exceed", data: { max: 2, actual: 4 } }]
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 2, actual: 4 },
+                    line: 4,
+                    column: 1,
+                    endLine: 8,
+                    endColumn: 16
+                }
+            ]
         },
         {
             code: [
@@ -123,7 +173,16 @@ ruleTester.run("max-lines", rule, {
                 "var z;"
             ].join("\n"),
             options: [{ max: 2, skipComments: true }],
-            errors: [{ messageId: "exceed", data: { max: 2, actual: 3 } }]
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 2, actual: 3 },
+                    line: 3,
+                    column: 1,
+                    endLine: 3,
+                    endColumn: 7
+                }
+            ]
         },
         {
             code: [
@@ -133,7 +192,16 @@ ruleTester.run("max-lines", rule, {
                 "var z;"
             ].join("\n"),
             options: [{ max: 2, skipComments: true }],
-            errors: [{ messageId: "exceed", data: { max: 2, actual: 3 } }]
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 2, actual: 3 },
+                    line: 4,
+                    column: 1,
+                    endLine: 4,
+                    endColumn: 7
+                }
+            ]
         },
         {
             code: [
@@ -147,17 +215,391 @@ ruleTester.run("max-lines", rule, {
                 " long comment*/"
             ].join("\n"),
             options: [{ max: 2, skipBlankLines: true }],
-            errors: [{ messageId: "exceed", data: { max: 2, actual: 6 } }]
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 2, actual: 6 },
+                    line: 4,
+                    column: 1,
+                    endLine: 8,
+                    endColumn: 16
+                }
+            ]
         },
         {
             code: "AAAAAAAA\n".repeat(301).trim(),
             options: [{}],
-            errors: [{ messageId: "exceed", data: { max: 300, actual: 301 } }]
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 300, actual: 301 },
+                    line: 301,
+                    column: 1,
+                    endLine: 301,
+                    endColumn: 9
+                }
+            ]
+        },
+        {
+
+            // Questionable. Makes sense to report this, and makes sense to not report this.
+            code: "",
+            options: [{ max: 0 }],
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 0, actual: 1 },
+                    line: 1,
+                    column: 1,
+                    endLine: 1,
+                    endColumn: 1
+                }
+            ]
+        },
+        {
+            code: " ",
+            options: [{ max: 0 }],
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 0, actual: 1 },
+                    line: 1,
+                    column: 1,
+                    endLine: 1,
+                    endColumn: 2
+                }
+            ]
+        },
+        {
+            code: "\n",
+            options: [{ max: 0 }],
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 0, actual: 1 },
+                    line: 1,
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 1
+                }
+            ]
         },
         {
             code: "A",
             options: [{ max: 0 }],
-            errors: [{ messageId: "exceed", data: { max: 0, actual: 1 } }]
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 0, actual: 1 },
+                    line: 1,
+                    column: 1,
+                    endLine: 1,
+                    endColumn: 2
+                }
+            ]
+        },
+        {
+            code: "A\n",
+            options: [{ max: 0 }],
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 0, actual: 1 },
+                    line: 1,
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 1
+                }
+            ]
+        },
+        {
+            code: "A\n ",
+            options: [{ max: 0 }],
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 0, actual: 2 },
+                    line: 1,
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 2
+                }
+            ]
+        },
+        {
+            code: "A\n ",
+            options: [{ max: 1 }],
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 1, actual: 2 },
+                    line: 2,
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 2
+                }
+            ]
+        },
+        {
+            code: "A\n\n",
+            options: [{ max: 1 }],
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 1, actual: 2 },
+                    line: 2,
+                    column: 1,
+                    endLine: 3,
+                    endColumn: 1
+                }
+            ]
+        },
+        {
+            code: ["var a = 'a'; ", "var x", "var c;", "console.log"].join(
+                "\n"
+            ),
+            options: [{ max: 2 }],
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 2, actual: 4 },
+                    line: 3,
+                    column: 1,
+                    endLine: 4,
+                    endColumn: 12
+                }
+            ]
+        },
+        {
+            code: "var a = 'a',\nc,\nx;\r",
+            options: [{ max: 2 }],
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 2, actual: 3 },
+                    line: 3,
+                    column: 1,
+                    endLine: 4,
+                    endColumn: 1
+                }
+            ]
+        },
+        {
+            code: "var a = 'a',\nc,\nx;\n",
+            options: [{ max: 2 }],
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 2, actual: 3 },
+                    line: 3,
+                    column: 1,
+                    endLine: 4,
+                    endColumn: 1
+                }
+            ]
+        },
+        {
+            code: "\n\nvar a = 'a',\nc,\nx;\n",
+            options: [{ max: 2, skipBlankLines: true }],
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 2, actual: 3 },
+                    line: 5,
+                    column: 1,
+                    endLine: 6,
+                    endColumn: 1
+                }
+            ]
+        },
+        {
+            code: [
+                "var a = 'a'; ",
+                "var x",
+                "var c;",
+                "console.log",
+                "// some block ",
+                "// comments"
+            ].join("\n"),
+            options: [{ max: 2, skipComments: true }],
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 2, actual: 4 },
+                    line: 3,
+                    column: 1,
+                    endLine: 6,
+                    endColumn: 12
+                }
+            ]
+        },
+        {
+            code: [
+                "var a = 'a'; ",
+                "var x",
+                "var c;",
+                "console.log",
+                "/* block comments */"
+            ].join("\n"),
+            options: [{ max: 2, skipComments: true }],
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 2, actual: 4 },
+                    line: 3,
+                    column: 1,
+                    endLine: 5,
+                    endColumn: 21
+                }
+            ]
+        },
+        {
+            code: [
+                "var a = 'a'; ",
+                "var x",
+                "var c;",
+                "console.log",
+                "/* block comments */\n"
+            ].join("\n"),
+            options: [{ max: 2, skipComments: true }],
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 2, actual: 4 },
+                    line: 3,
+                    column: 1,
+                    endLine: 6,
+                    endColumn: 1
+                }
+            ]
+        },
+        {
+            code: [
+                "var a = 'a'; ",
+                "var x",
+                "var c;",
+                "console.log",
+                "/** block \n\n comments */"
+            ].join("\n"),
+            options: [{ max: 2, skipComments: true }],
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 2, actual: 4 },
+                    line: 3,
+                    column: 1,
+                    endLine: 7,
+                    endColumn: 13
+                }
+            ]
+        },
+        {
+            code: [
+                "var a = 'a'; ",
+                "",
+                "",
+                "// comment"
+            ].join("\n"),
+            options: [{ max: 2, skipComments: true }],
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 2, actual: 3 },
+                    line: 3,
+                    column: 1,
+                    endLine: 4,
+                    endColumn: 11
+                }
+            ]
+        },
+        {
+            code: [
+                "var a = 'a'; ",
+                "var x",
+                "\n",
+                "var c;",
+                "console.log",
+                "\n"
+            ].join("\n"),
+            options: [{ max: 2, skipBlankLines: true }],
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 2, actual: 4 },
+                    line: 5,
+                    column: 1,
+                    endLine: 8,
+                    endColumn: 1
+                }
+            ]
+        },
+        {
+            code: [
+                "var a = 'a'; ",
+                "\n",
+                "var x",
+                "var c;",
+                "console.log",
+                "\n"
+            ].join("\n"),
+            options: [{ max: 2, skipBlankLines: true }],
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 2, actual: 4 },
+                    line: 5,
+                    column: 1,
+                    endLine: 8,
+                    endColumn: 1
+                }
+            ]
+        },
+        {
+            code: [
+                "var a = 'a'; ",
+                "//",
+                "var x",
+                "var c;",
+                "console.log",
+                "//"
+            ].join("\n"),
+            options: [{ max: 2, skipComments: true }],
+            errors: [
+                {
+                    messageId: "exceed",
+                    data: { max: 2, actual: 4 },
+                    line: 4,
+                    column: 1,
+                    endLine: 6,
+                    endColumn: 3
+                }
+            ]
+        },
+        {
+            code: ["// hello world", "/*hello", " world 2 */", "var a,", "b", "// hh", "c,", "e,", "f;"].join("\n"),
+            options: [{ max: 2, skipComments: true }],
+            errors: [{
+                data: { max: 2, actual: 5 },
+                messageId: "exceed",
+                line: 7,
+                column: 1,
+                endLine: 9,
+                endColumn: 3
+
+            }]
+        },
+        {
+            code: ["", "var x = '';", "", "// comment", "", "var b = '',", "c,", "d,", "e", "", "// comment"].join("\n"),
+            options: [{ max: 2, skipComments: true, skipBlankLines: true }],
+            errors: [{
+                data: { max: 2, actual: 5 },
+                messageId: "exceed",
+                line: 7,
+                column: 1,
+                endLine: 11,
+                endColumn: 11
+            }]
         }
+
     ]
 });
index ec85c4bf6fd0eb2e878b89a55ac90c5a5648a84a..5953c87c24aedcc0717634cfe623d0b911b3820e 100644 (file)
@@ -71,7 +71,39 @@ ruleTester.run("new-cap", rule, {
         { code: "var x = new foo.bar(42);", options: [{ newIsCapExceptionPattern: "^foo\\.." }] },
         { code: "var x = new foo.bar(42);", options: [{ properties: false }] },
         { code: "var x = Foo.bar(42);", options: [{ properties: false }] },
-        { code: "var x = foo.Bar(42);", options: [{ capIsNew: false, properties: false }] }
+        { code: "var x = foo.Bar(42);", options: [{ capIsNew: false, properties: false }] },
+
+        // Optional chaining
+        {
+            code: "foo?.bar();",
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "(foo?.bar)();",
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "new (foo?.Bar)();",
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "(foo?.Bar)();",
+            options: [{ properties: false }],
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "new (foo?.bar)();",
+            options: [{ properties: false }],
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "Date?.UTC();",
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "(Date?.UTC)();",
+            parserOptions: { ecmaVersion: 2020 }
+        }
     ],
     invalid: [
         {
@@ -302,6 +334,23 @@ ruleTester.run("new-cap", rule, {
 
             options: [{ newIsCapExceptionPattern: "^foo\\.." }],
             errors: [{ type: "NewExpression", messageId: "lower" }]
+        },
+
+        // Optional chaining
+        {
+            code: "new (foo?.bar)();",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "lower", column: 11, endColumn: 14 }]
+        },
+        {
+            code: "foo?.Bar();",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "upper", column: 6, endColumn: 9 }]
+        },
+        {
+            code: "(foo?.Bar)();",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "upper", column: 7, endColumn: 10 }]
         }
     ]
 });
index 28d45c69b820ee4caae48ca280dc1dab7d47fbb1..2a26937ae62b2a23c9b1642841d4ee980793669f 100644 (file)
@@ -340,5 +340,69 @@ ruleTester.run("newline-per-chained-call", rule, {
             endLine: 1,
             endColumn: 35
         }]
-    }]
+    },
+
+    // Optional chaining
+    {
+        code: "obj?.foo1()?.foo2()?.foo3()",
+        output: "obj?.foo1()\n?.foo2()\n?.foo3()",
+        options: [{ ignoreChainWithDepth: 1 }],
+        parserOptions: { ecmaVersion: 2020 },
+        errors: [
+            { messageId: "expected", data: { callee: "?.foo2" } },
+            { messageId: "expected", data: { callee: "?.foo3" } }
+        ]
+    },
+    {
+        code: "(obj?.foo1()?.foo2)()?.foo3()",
+        output: "(obj?.foo1()\n?.foo2)()\n?.foo3()",
+        options: [{ ignoreChainWithDepth: 1 }],
+        parserOptions: { ecmaVersion: 2020 },
+        errors: [
+            { messageId: "expected", data: { callee: "?.foo2" } },
+            { messageId: "expected", data: { callee: "?.foo3" } }
+        ]
+    },
+    {
+        code: "(obj?.foo1())?.foo2()?.foo3()",
+        output: "(obj?.foo1())\n?.foo2()\n?.foo3()",
+        options: [{ ignoreChainWithDepth: 1 }],
+        parserOptions: { ecmaVersion: 2020 },
+        errors: [
+            { messageId: "expected", data: { callee: "?.foo2" } },
+            { messageId: "expected", data: { callee: "?.foo3" } }
+        ]
+    },
+    {
+        code: "obj?.[foo1]()?.[foo2]()?.[foo3]()",
+        output: "obj?.[foo1]()\n?.[foo2]()\n?.[foo3]()",
+        options: [{ ignoreChainWithDepth: 1 }],
+        parserOptions: { ecmaVersion: 2020 },
+        errors: [
+            { messageId: "expected", data: { callee: "?.[foo2]" } },
+            { messageId: "expected", data: { callee: "?.[foo3]" } }
+        ]
+    },
+    {
+        code: "(obj?.[foo1]()?.[foo2])()?.[foo3]()",
+        output: "(obj?.[foo1]()\n?.[foo2])()\n?.[foo3]()",
+        options: [{ ignoreChainWithDepth: 1 }],
+        parserOptions: { ecmaVersion: 2020 },
+        errors: [
+            { messageId: "expected", data: { callee: "?.[foo2]" } },
+            { messageId: "expected", data: { callee: "?.[foo3]" } }
+        ]
+    },
+    {
+        code: "(obj?.[foo1]())?.[foo2]()?.[foo3]()",
+        output: "(obj?.[foo1]())\n?.[foo2]()\n?.[foo3]()",
+        options: [{ ignoreChainWithDepth: 1 }],
+        parserOptions: { ecmaVersion: 2020 },
+        errors: [
+            { messageId: "expected", data: { callee: "?.[foo2]" } },
+            { messageId: "expected", data: { callee: "?.[foo3]" } }
+        ]
+    }
+
+    ]
 });
index 4851d5b0a7a294a0b393cd073eb6a7e4661ae608..03fcddb6994fd0c2e355498c2bad7c1a02ac7122 100644 (file)
@@ -124,6 +124,18 @@ ruleTester.run("no-alert", rule, {
             code: "function foo() { var globalThis = bar; globalThis.alert(); }\nglobalThis.alert();",
             env: { es2020: true },
             errors: [{ messageId: "unexpected", data: { name: "alert" }, type: "CallExpression", line: 2, column: 1 }]
+        },
+
+        // Optional chaining
+        {
+            code: "window?.alert(foo)",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpected", data: { name: "alert" } }]
+        },
+        {
+            code: "(window?.alert)(foo)",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpected", data: { name: "alert" } }]
         }
     ]
 });
index ae3e8a8084a031c71aaca4c58940f077a2b1bead..25cc286f95e445218095e195303ba2f4671eb6ce 100644 (file)
@@ -22,7 +22,12 @@ ruleTester.run("no-bitwise", rule, {
     valid: [
         "a + b",
         "!a",
+        "a && b",
+        "a || b",
         "a += b",
+        { code: "a &&= b", parserOptions: { ecmaVersion: 2021 } },
+        { code: "a ||= b", parserOptions: { ecmaVersion: 2021 } },
+        { code: "a ??= b", parserOptions: { ecmaVersion: 2021 } },
         { code: "~[1, 2, 3].indexOf(1)", options: [{ allow: ["~"] }] },
         { code: "~1<<2 === -8", options: [{ allow: ["~", "<<"] }] },
         { code: "a|0", options: [{ int32Hint: true }] },
index 2aa4fa73dff5bb065aacfb63c153e6a1bac1de5d..b83b337a65febe827d1a35238153a1bd42acecda 100644 (file)
@@ -74,12 +74,11 @@ ruleTester.run("no-constant-condition", rule, {
         "if(true && typeof abc==='string'){}",
 
         // #11181, string literals
-        "if('str' || a){}",
         "if('str1' && a){}",
         "if(a && 'str'){}",
-        "if('str' || abc==='str'){}",
 
         // #11306
+        "if ((foo || true) === 'baz') {}",
         "if ((foo || 'bar') === 'baz') {}",
         "if ((foo || 'bar') !== 'baz') {}",
         "if ((foo || 'bar') == 'baz') {}",
@@ -90,6 +89,19 @@ ruleTester.run("no-constant-condition", rule, {
         "if ((foo || 233) <= 666) {}",
         "if ((key || 'k') in obj) {}",
         "if ((foo || {}) instanceof obj) {}",
+        "if ((foo || 'bar' || 'bar') === 'bar');",
+        {
+            code: "if ((foo || 1n) === 'baz') {}",
+            parserOptions: { ecmaVersion: 11 }
+        },
+        {
+            code: "if (a && 0n || b);",
+            parserOptions: { ecmaVersion: 11 }
+        },
+        {
+            code: "if(1n && a){};",
+            parserOptions: { ecmaVersion: 11 }
+        },
 
         // #12225
         "if ('' + [y] === '' + [ty]) {}",
@@ -121,11 +133,14 @@ ruleTester.run("no-constant-condition", rule, {
         { code: "for(;`foo`;);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
         { code: "for(;`foo${bar}`;);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
         { code: "do{}while(true)", errors: [{ messageId: "unexpected", type: "Literal" }] },
+        { code: "do{}while('1')", errors: [{ messageId: "unexpected", type: "Literal" }] },
+        { code: "do{}while(0)", errors: [{ messageId: "unexpected", type: "Literal" }] },
         { code: "do{}while(t = -2)", errors: [{ messageId: "unexpected", type: "AssignmentExpression" }] },
         { code: "do{}while(``)", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
         { code: "do{}while(`foo`)", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
         { code: "do{}while(`foo${bar}`)", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
         { code: "true ? 1 : 2;", errors: [{ messageId: "unexpected", type: "Literal" }] },
+        { code: "1 ? 1 : 2;", errors: [{ messageId: "unexpected", type: "Literal" }] },
         { code: "q = 0 ? 1 : 2;", errors: [{ messageId: "unexpected", type: "Literal" }] },
         { code: "(q = 0) ? 1 : 2;", errors: [{ messageId: "unexpected", type: "AssignmentExpression" }] },
         { code: "`` ? 1 : 2;", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
@@ -133,6 +148,7 @@ ruleTester.run("no-constant-condition", rule, {
         { code: "`foo${bar}` ? 1 : 2;", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
         { code: "if(-2);", errors: [{ messageId: "unexpected", type: "UnaryExpression" }] },
         { code: "if(true);", errors: [{ messageId: "unexpected", type: "Literal" }] },
+        { code: "if(1);", errors: [{ messageId: "unexpected", type: "Literal" }] },
         { code: "if({});", errors: [{ messageId: "unexpected", type: "ObjectExpression" }] },
         { code: "if(0 < 1);", errors: [{ messageId: "unexpected", type: "BinaryExpression" }] },
         { code: "if(0 || 1);", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
@@ -143,6 +159,7 @@ ruleTester.run("no-constant-condition", rule, {
         { code: "if(`${'bar'}`);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
         { code: "if(`${'bar' + `foo`}`);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
         { code: "if(`foo${false || true}`);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
+        { code: "if(`foo${0 || 1}`);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
         { code: "if(`foo${bar}`);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
         { code: "if(`${bar}foo`);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
 
@@ -152,6 +169,7 @@ ruleTester.run("no-constant-condition", rule, {
         { code: "while(x = 1);", errors: [{ messageId: "unexpected", type: "AssignmentExpression" }] },
         { code: "while(function(){});", errors: [{ messageId: "unexpected", type: "FunctionExpression" }] },
         { code: "while(true);", errors: [{ messageId: "unexpected", type: "Literal" }] },
+        { code: "while(1);", errors: [{ messageId: "unexpected", type: "Literal" }] },
         { code: "while(() => {});", errors: [{ messageId: "unexpected", type: "ArrowFunctionExpression" }] },
         { code: "while(`foo`);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
         { code: "while(``);", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
@@ -180,12 +198,15 @@ ruleTester.run("no-constant-condition", rule, {
         // #5693
         { code: "if(false && abc==='str'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
         { code: "if(true || abc==='str'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
+        { code: "if(1 || abc==='str'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
         { code: "if(abc==='str' || true){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
         { code: "if(abc==='str' || true || def ==='str'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
         { code: "if(false || true){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
         { code: "if(typeof abc==='str' || true){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
 
         // #11181, string literals
+        { code: "if('str' || a){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
+        { code: "if('str' || abc==='str'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
         { code: "if('str1' || 'str2'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
         { code: "if('str1' && 'str2'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
         { code: "if(abc==='str' || 'str'){}", errors: [{ messageId: "unexpected", type: "LogicalExpression" }] },
@@ -272,6 +293,17 @@ ruleTester.run("no-constant-condition", rule, {
         {
             code: "if ([,] + ''){}",
             errors: [{ messageId: "unexpected", type: "BinaryExpression" }]
-        }
+        },
+
+        // #13238
+        { code: "if(/foo/ui);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "Literal" }] },
+        { code: "if(0n);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "Literal" }] },
+        { code: "if(0b0n);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "Literal" }] },
+        { code: "if(0o0n);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "Literal" }] },
+        { code: "if(0x0n);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "Literal" }] },
+        { code: "if(0b1n);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "Literal" }] },
+        { code: "if(0o1n);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "Literal" }] },
+        { code: "if(0x1n);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "Literal" }] },
+        { code: "if(0x1n || foo);", parserOptions: { ecmaVersion: 11 }, errors: [{ messageId: "unexpected", type: "LogicalExpression" }] }
     ]
 });
index a6062a2a24bdb40861c64dcd0f02d64d21c88778..6c650db485f344cd7f23a697fc71037d57480bc0 100644 (file)
@@ -33,7 +33,9 @@ ruleTester.run("no-dupe-keys", rule, {
         { code: "var x = { get a() {}, set a (value) {} };", parserOptions: { ecmaVersion: 6 } },
         { code: "var x = { a: 1, b: { a: 2 } };", parserOptions: { ecmaVersion: 6 } },
         { code: "var x = ({ null: 1, [/(?<zero>0)/]: 2 })", parserOptions: { ecmaVersion: 2018 } },
-        { code: "var {a, a} = obj", parserOptions: { ecmaVersion: 6 } }
+        { code: "var {a, a} = obj", parserOptions: { ecmaVersion: 6 } },
+        "var x = { 012: 1, 12: 2 };",
+        { code: "var x = { 1_0: 1, 1: 2 };", parserOptions: { ecmaVersion: 2021 } }
     ],
     invalid: [
         { code: "var x = { a: b, ['a']: b };", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "a" }, type: "ObjectExpression" }] },
@@ -41,6 +43,11 @@ ruleTester.run("no-dupe-keys", rule, {
         { code: "var x = { '': 1, '': 2 };", errors: [{ messageId: "unexpected", data: { name: "" }, type: "ObjectExpression" }] },
         { code: "var x = { '': 1, [``]: 2 };", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "" }, type: "ObjectExpression" }] },
         { code: "var foo = { 0x1: 1, 1: 2};", errors: [{ messageId: "unexpected", data: { name: "1" }, type: "ObjectExpression" }] },
+        { code: "var x = { 012: 1, 10: 2 };", errors: [{ messageId: "unexpected", data: { name: "10" }, type: "ObjectExpression" }] },
+        { code: "var x = { 0b1: 1, 1: 2 };", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "1" }, type: "ObjectExpression" }] },
+        { code: "var x = { 0o1: 1, 1: 2 };", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "1" }, type: "ObjectExpression" }] },
+        { code: "var x = { 1n: 1, 1: 2 };", parserOptions: { ecmaVersion: 2020 }, errors: [{ messageId: "unexpected", data: { name: "1" }, type: "ObjectExpression" }] },
+        { code: "var x = { 1_0: 1, 10: 2 };", parserOptions: { ecmaVersion: 2021 }, errors: [{ messageId: "unexpected", data: { name: "10" }, type: "ObjectExpression" }] },
         { code: "var x = { \"z\": 1, z: 2 };", errors: [{ messageId: "unexpected", data: { name: "z" }, type: "ObjectExpression" }] },
         { code: "var foo = {\n  bar: 1,\n  bar: 1,\n}", errors: [{ messageId: "unexpected", data: { name: "bar" }, line: 3, column: 3 }] },
         { code: "var x = { a: 1, get a() {} };", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "a" }, type: "ObjectExpression" }] },
index a6e8e0ff34c91443c83c9fa4562d4d62ceea0f12..42dbda3f18dd9371d65dd1c8264c774af166cad6 100644 (file)
@@ -128,6 +128,66 @@ ruleTester.run("no-duplicate-case", rule, {
                     column: 74
                 }
             ]
+        },
+        {
+            code: "var a = 1, p = {p: {p1: 1, p2: 1}}; switch (a) {case p.p.p1: break; case p. p // comment\n .p1: break; default: break;}",
+            errors: [{
+                messageId: "unexpected",
+                type: "SwitchCase",
+                column: 69
+            }]
+        },
+        {
+            code: "var a = 1, p = {p: {p1: 1, p2: 1}}; switch (a) {case p .p\n/* comment */\n.p1: break; case p.p.p1: break; default: break;}",
+            errors: [{
+                messageId: "unexpected",
+                type: "SwitchCase",
+                line: 3,
+                column: 13
+            }]
+        },
+        {
+            code: "var a = 1, p = {p: {p1: 1, p2: 1}}; switch (a) {case p .p\n/* comment */\n.p1: break; case p. p // comment\n .p1: break; default: break;}",
+            errors: [{
+                messageId: "unexpected",
+                type: "SwitchCase",
+                line: 3,
+                column: 13
+            }]
+        },
+        {
+            code: "var a = 1, p = {p: {p1: 1, p2: 1}}; switch (a) {case p.p.p1: break; case p. p // comment\n .p1: break; case p .p\n/* comment */\n.p1: break; default: break;}",
+            errors: [
+                {
+                    messageId: "unexpected",
+                    type: "SwitchCase",
+                    line: 1,
+                    column: 69
+                },
+                {
+                    messageId: "unexpected",
+                    type: "SwitchCase",
+                    line: 2,
+                    column: 14
+                }
+            ]
+        },
+        {
+            code: "var a = 1, f = function(s) { return { p1: s } }; switch (a) {case f(a + 1).p1: break; case f(a+1).p1: break; default: break;}",
+            errors: [{
+                messageId: "unexpected",
+                type: "SwitchCase",
+                column: 87
+            }]
+        },
+        {
+            code: "var a = 1, f = function(s) { return { p1: s } }; switch (a) {case f(\na + 1 // comment\n).p1: break; case f(a+1)\n.p1: break; default: break;}",
+            errors: [{
+                messageId: "unexpected",
+                type: "SwitchCase",
+                line: 3,
+                column: 14
+            }]
         }
     ]
 });
index ad184bd9370a887879a0e5ca26e244ba7da7da4e..bc8d38471bbebe4c4c570674c2fc4448849657e5 100644 (file)
@@ -67,7 +67,10 @@ ruleTester.run("no-eval", rule, {
         { code: "(0, globalThis['eval'])('foo')", options: [{ allowIndirect: true }], env: { es2020: true } },
         { code: "var EVAL = globalThis.eval; EVAL('foo')", options: [{ allowIndirect: true }] },
         { code: "function foo() { globalThis.eval('foo') }", options: [{ allowIndirect: true }], env: { es2020: true } },
-        { code: "globalThis.globalThis.eval('foo');", options: [{ allowIndirect: true }], env: { es2020: true } }
+        { code: "globalThis.globalThis.eval('foo');", options: [{ allowIndirect: true }], env: { es2020: true } },
+        { code: "eval?.('foo')", options: [{ allowIndirect: true }], parserOptions: { ecmaVersion: 2020 } },
+        { code: "window?.eval('foo')", options: [{ allowIndirect: true }], parserOptions: { ecmaVersion: 2020 }, env: { browser: true } },
+        { code: "(window?.eval)('foo')", options: [{ allowIndirect: true }], parserOptions: { ecmaVersion: 2020 }, env: { browser: true } }
     ],
 
     invalid: [
@@ -100,6 +103,26 @@ ruleTester.run("no-eval", rule, {
         { code: "globalThis.globalThis.eval('foo')", env: { es2020: true }, errors: [{ messageId: "unexpected", type: "CallExpression", column: 23, endColumn: 27 }] },
         { code: "globalThis.globalThis['eval']('foo')", env: { es2020: true }, errors: [{ messageId: "unexpected", type: "CallExpression", column: 23, endColumn: 29 }] },
         { code: "(0, globalThis.eval)('foo')", env: { es2020: true }, errors: [{ messageId: "unexpected", type: "MemberExpression", column: 16, endColumn: 20 }] },
-        { code: "(0, globalThis['eval'])('foo')", env: { es2020: true }, errors: [{ messageId: "unexpected", type: "MemberExpression", column: 16, endColumn: 22 }] }
+        { code: "(0, globalThis['eval'])('foo')", env: { es2020: true }, errors: [{ messageId: "unexpected", type: "MemberExpression", column: 16, endColumn: 22 }] },
+
+        // Optional chaining
+        {
+            code: "window?.eval('foo')",
+            parserOptions: { ecmaVersion: 2020 },
+            globals: { window: "readonly" },
+            errors: [{ messageId: "unexpected" }]
+        },
+        {
+            code: "(window?.eval)('foo')",
+            parserOptions: { ecmaVersion: 2020 },
+            globals: { window: "readonly" },
+            errors: [{ messageId: "unexpected" }]
+        },
+        {
+            code: "(window?.window).eval('foo')",
+            parserOptions: { ecmaVersion: 2020 },
+            globals: { window: "readonly" },
+            errors: [{ messageId: "unexpected" }]
+        }
     ]
 });
index a38177431d27412917e0a523361502049c948f2c..ca5b430fafe4e7b53a3d03206dd7477c61a09d41 100644 (file)
@@ -37,6 +37,7 @@ ruleTester.run("no-extend-native", rule, {
             code: "Object.prototype.g = 0",
             options: [{ exceptions: ["Object"] }]
         },
+        "obj[Object.prototype] = 0",
 
         // https://github.com/eslint/eslint/issues/4438
         "Object.defineProperty()",
@@ -47,6 +48,12 @@ 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: [{
@@ -137,5 +144,46 @@ ruleTester.run("no-extend-native", rule, {
             data: { builtin: "Object" },
             type: "AssignmentExpression"
         }]
-    }]
+    },
+
+    // Optional chaining
+    {
+        code: "(Object?.prototype).p = 0",
+        parserOptions: { ecmaVersion: 2020 },
+        errors: [{ messageId: "unexpected", data: { builtin: "Object" } }]
+    },
+    {
+        code: "Object.defineProperty(Object?.prototype, 'p', { value: 0 })",
+        parserOptions: { ecmaVersion: 2020 },
+        errors: [{ messageId: "unexpected", data: { builtin: "Object" } }]
+    },
+    {
+        code: "Object?.defineProperty(Object.prototype, 'p', { value: 0 })",
+        parserOptions: { ecmaVersion: 2020 },
+        errors: [{ messageId: "unexpected", data: { builtin: "Object" } }]
+    },
+    {
+        code: "(Object?.defineProperty)(Object.prototype, 'p', { value: 0 })",
+        parserOptions: { ecmaVersion: 2020 },
+        errors: [{ messageId: "unexpected", data: { builtin: "Object" } }]
+    },
+
+    // Logical assignments
+    {
+        code: "Array.prototype.p &&= 0",
+        parserOptions: { ecmaVersion: 2021 },
+        errors: [{ messageId: "unexpected", data: { builtin: "Array" } }]
+    },
+    {
+        code: "Array.prototype.p ||= 0",
+        parserOptions: { ecmaVersion: 2021 },
+        errors: [{ messageId: "unexpected", data: { builtin: "Array" } }]
+    },
+    {
+        code: "Array.prototype.p ??= 0",
+        parserOptions: { ecmaVersion: 2021 },
+        errors: [{ messageId: "unexpected", data: { builtin: "Array" } }]
+    }
+
+    ]
 });
index 121979671765d18a15174b6f57136ab453a7cbd8..8422f3de77172e6176380ad0d705627ec7ce12a4 100644 (file)
@@ -102,6 +102,16 @@ ruleTester.run("no-extra-bind", rule, {
             output: "var a = function() { (function(){ (function(){ this.d }.bind(c)) }) }",
             errors: [{ messageId: "unexpected", type: "CallExpression", column: 71 }]
         },
+        {
+            code: "var a = (function() { return 1; }).bind(this)",
+            output: "var a = (function() { return 1; })",
+            errors
+        },
+        {
+            code: "var a = (function() { return 1; }.bind)(this)",
+            output: "var a = (function() { return 1; })",
+            errors
+        },
 
         // Should not autofix if bind expression args have side effects
         {
@@ -180,6 +190,44 @@ ruleTester.run("no-extra-bind", rule, {
             code: "var a = function() {}.bind(b)/**/",
             output: "var a = function() {}/**/",
             errors
+        },
+
+        // Optional chaining
+        {
+            code: "var a = function() { return 1; }.bind?.(b)",
+            output: "var a = function() { return 1; }",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpected" }]
+        },
+        {
+            code: "var a = function() { return 1; }?.bind(b)",
+            output: "var a = function() { return 1; }",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpected" }]
+        },
+        {
+            code: "var a = (function() { return 1; }?.bind)(b)",
+            output: "var a = (function() { return 1; })",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpected" }]
+        },
+        {
+            code: "var a = function() { return 1; }['bind']?.(b)",
+            output: "var a = function() { return 1; }",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpected" }]
+        },
+        {
+            code: "var a = function() { return 1; }?.['bind'](b)",
+            output: "var a = function() { return 1; }",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpected" }]
+        },
+        {
+            code: "var a = (function() { return 1; }?.['bind'])(b)",
+            output: "var a = (function() { return 1; })",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpected" }]
         }
     ]
 });
index 70ec8bd4f61d328c07164dbb633a1161c6a14430..8dda6992d2126da15d6e7e82245e6ab9e1afd656 100644 (file)
@@ -2408,6 +2408,21 @@ ruleTester.run("no-extra-boolean-cast", rule, {
             options: [{ enforceForLogicalOperands: true }],
             parserOptions: { ecmaVersion: 2020 },
             errors: [{ messageId: "unexpectedCall", type: "CallExpression" }]
+        },
+
+        // Optional chaining
+        {
+            code: "if (Boolean?.(foo)) ;",
+            output: "if (foo) ;",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpectedCall" }]
+        },
+        {
+            code: "if (Boolean?.(a ?? b) || c) {}",
+            output: "if ((a ?? b) || c) {}",
+            options: [{ enforceForLogicalOperands: true }],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpectedCall" }]
         }
     ]
 });
index 91099f8cba04b0ff7041dadf6dd3bf7745c09db7..c0d0387ec74d51c0de457a9f73cb7555e660a86b 100644 (file)
@@ -44,7 +44,7 @@ function invalid(code, output, type, line, config) {
 
 const ruleTester = new RuleTester({
     parserOptions: {
-        ecmaVersion: 2020,
+        ecmaVersion: 2021,
         ecmaFeatures: {
             jsx: true
         }
@@ -190,6 +190,13 @@ ruleTester.run("no-extra-parens", rule, {
 
         // special cases
         "(0).a",
+        "(123).a",
+        "(08).a",
+        "(09).a",
+        "(018).a",
+        "(012934).a",
+        "(5_000).a",
+        "(5_000_00).a",
         "(function(){ }())",
         "({a: function(){}}.a());",
         "({a:0}.a ? b : c)",
@@ -356,6 +363,15 @@ ruleTester.run("no-extra-parens", rule, {
             "}"
         ].join("\n"),
 
+        // linebreaks before postfix update operators are not allowed
+        "(a\n)++",
+        "(a\n)--",
+        "(a\n\n)++",
+        "(a.b\n)--",
+        "(a\n.b\n)++",
+        "(a[\nb\n]\n)--",
+        "(a[b]\n\n)++",
+
         // async/await
         "async function a() { await (a + b) }",
         "async function a() { await (a + await b) }",
@@ -612,6 +628,14 @@ ruleTester.run("no-extra-parens", rule, {
         "for (; a; a); a; a;",
         "for (let a = (b && c) === d; ;);",
 
+        "new (a()).b.c;",
+        "new (a().b).c;",
+        "new (a().b.c);",
+        "new (a().b().d);",
+        "new a().b().d;",
+        "new (a(b()).c)",
+        "new (a.b()).c",
+
         // Nullish coalescing
         { code: "var v = (a ?? b) || c", parserOptions: { ecmaVersion: 2020 } },
         { code: "var v = a ?? (b || c)", parserOptions: { ecmaVersion: 2020 } },
@@ -620,7 +644,32 @@ ruleTester.run("no-extra-parens", rule, {
         { code: "var v = (a || b) ?? c", parserOptions: { ecmaVersion: 2020 } },
         { code: "var v = a || (b ?? c)", parserOptions: { ecmaVersion: 2020 } },
         { code: "var v = (a && b) ?? c", parserOptions: { ecmaVersion: 2020 } },
-        { code: "var v = a && (b ?? c)", parserOptions: { ecmaVersion: 2020 } }
+        { code: "var v = a && (b ?? c)", parserOptions: { ecmaVersion: 2020 } },
+
+        // Optional chaining
+        { code: "var v = (obj?.aaa).bbb", parserOptions: { ecmaVersion: 2020 } },
+        { code: "var v = (obj?.aaa)()", parserOptions: { ecmaVersion: 2020 } },
+        { code: "var v = new (obj?.aaa)()", parserOptions: { ecmaVersion: 2020 } },
+        { code: "var v = new (obj?.aaa)", parserOptions: { ecmaVersion: 2020 } },
+        { code: "var v = (obj?.aaa)`template`", parserOptions: { ecmaVersion: 2020 } },
+        { code: "var v = (obj?.()).bbb", parserOptions: { ecmaVersion: 2020 } },
+        { code: "var v = (obj?.())()", parserOptions: { ecmaVersion: 2020 } },
+        { code: "var v = new (obj?.())()", parserOptions: { ecmaVersion: 2020 } },
+        { code: "var v = new (obj?.())", parserOptions: { ecmaVersion: 2020 } },
+        { code: "var v = (obj?.())`template`", parserOptions: { ecmaVersion: 2020 } },
+        { code: "(obj?.aaa).bbb = 0", parserOptions: { ecmaVersion: 2020 } },
+        { code: "var foo = (function(){})?.()", parserOptions: { ecmaVersion: 2020 } },
+        { code: "var foo = (function(){}?.())", parserOptions: { ecmaVersion: 2020 } },
+        {
+            code: "var foo = (function(){})?.call()",
+            options: ["all", { enforceForFunctionPrototypeMethods: false }],
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "var foo = (function(){}?.call())",
+            options: ["all", { enforceForFunctionPrototypeMethods: false }],
+            parserOptions: { ecmaVersion: 2020 }
+        }
     ],
 
     invalid: [
@@ -715,6 +764,18 @@ ruleTester.run("no-extra-parens", rule, {
         invalid("+((bar-foo))", "+(bar-foo)", "BinaryExpression"),
         invalid("++(foo)", "++foo", "Identifier"),
         invalid("--(foo)", "--foo", "Identifier"),
+        invalid("++\n(foo)", "++\nfoo", "Identifier"),
+        invalid("--\n(foo)", "--\nfoo", "Identifier"),
+        invalid("++(\nfoo)", "++\nfoo", "Identifier"),
+        invalid("--(\nfoo)", "--\nfoo", "Identifier"),
+        invalid("(foo)++", "foo++", "Identifier"),
+        invalid("(foo)--", "foo--", "Identifier"),
+        invalid("((foo)\n)++", "(foo\n)++", "Identifier"),
+        invalid("((foo\n))--", "(foo\n)--", "Identifier"),
+        invalid("((foo\n)\n)++", "(foo\n\n)++", "Identifier"),
+        invalid("(a\n.b)--", "a\n.b--", "MemberExpression"),
+        invalid("(a.\nb)++", "a.\nb++", "MemberExpression"),
+        invalid("(a\n[\nb\n])--", "a\n[\nb\n]--", "MemberExpression"),
         invalid("(a || b) ? c : d", "a || b ? c : d", "LogicalExpression"),
         invalid("a ? (b = c) : d", "a ? b = c : d", "AssignmentExpression"),
         invalid("a ? b : (c = d)", "a ? b : c = d", "AssignmentExpression"),
@@ -742,9 +803,14 @@ ruleTester.run("no-extra-parens", rule, {
         invalid("(a).b", "a.b", "Identifier"),
         invalid("(0)[a]", "0[a]", "Literal"),
         invalid("(0.0).a", "0.0.a", "Literal"),
+        invalid("(123.4).a", "123.4.a", "Literal"),
+        invalid("(0.0_0).a", "0.0_0.a", "Literal"),
         invalid("(0xBEEF).a", "0xBEEF.a", "Literal"),
+        invalid("(0xBE_EF).a", "0xBE_EF.a", "Literal"),
         invalid("(1e6).a", "1e6.a", "Literal"),
         invalid("(0123).a", "0123.a", "Literal"),
+        invalid("(08.1).a", "08.1.a", "Literal"),
+        invalid("(09.).a", "09..a", "Literal"),
         invalid("a[(function() {})]", "a[function() {}]", "FunctionExpression"),
         invalid("new (function(){})", "new function(){}", "FunctionExpression"),
         invalid("new (\nfunction(){}\n)", "new \nfunction(){}\n", "FunctionExpression", 1),
@@ -759,6 +825,7 @@ ruleTester.run("no-extra-parens", rule, {
         invalid("(new foo(bar)).baz", "new foo(bar).baz", "NewExpression"),
         invalid("(new foo.bar()).baz", "new foo.bar().baz", "NewExpression"),
         invalid("(new foo.bar()).baz()", "new foo.bar().baz()", "NewExpression"),
+        invalid("new a[(b()).c]", "new a[b().c]", "CallExpression"),
 
         invalid("(a)()", "a()", "Identifier"),
         invalid("(a.b)()", "a.b()", "MemberExpression"),
@@ -772,6 +839,14 @@ ruleTester.run("no-extra-parens", rule, {
         invalid("((new A))()", "(new A)()", "NewExpression"),
         invalid("new (foo\n.baz\n.bar\n.foo.baz)", "new foo\n.baz\n.bar\n.foo.baz", "MemberExpression"),
         invalid("new (foo.baz.bar.baz)", "new foo.baz.bar.baz", "MemberExpression"),
+        invalid("new ((a.b())).c", "new (a.b()).c", "CallExpression"),
+        invalid("new ((a().b)).c", "new (a().b).c", "MemberExpression"),
+        invalid("new ((a().b().d))", "new (a().b().d)", "MemberExpression"),
+        invalid("new ((a())).b.d", "new (a()).b.d", "CallExpression"),
+        invalid("new (a.b).d;", "new a.b.d;", "MemberExpression"),
+        invalid("(a().b).d;", "a().b.d;", "MemberExpression"),
+        invalid("(a.b()).d;", "a.b().d;", "CallExpression"),
+        invalid("(a.b).d;", "a.b.d;", "MemberExpression"),
 
         invalid("0, (_ => 0)", "0, _ => 0", "ArrowFunctionExpression", 1),
         invalid("(_ => 0), 0", "_ => 0, 0", "ArrowFunctionExpression", 1),
@@ -2698,6 +2773,34 @@ ruleTester.run("no-extra-parens", rule, {
             output: "var v = a | b ?? c | d",
             parserOptions: { ecmaVersion: 2020 },
             errors: [{ messageId: "unexpected" }]
+        },
+
+        // Optional chaining
+        {
+            code: "var v = (obj?.aaa)?.aaa",
+            output: "var v = obj?.aaa?.aaa",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpected" }]
+        },
+        {
+            code: "var v = (obj.aaa)?.aaa",
+            output: "var v = obj.aaa?.aaa",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpected" }]
+        },
+        {
+            code: "var foo = (function(){})?.call()",
+            output: "var foo = function(){}?.call()",
+            options: ["all", { enforceForFunctionPrototypeMethods: true }],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpected" }]
+        },
+        {
+            code: "var foo = (function(){}?.call())",
+            output: "var foo = function(){}?.call()",
+            options: ["all", { enforceForFunctionPrototypeMethods: true }],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpected" }]
         }
     ]
 });
index 662342158fba11a074e795f9b745547fe892d12b..85a6bdcc5bb0808197c07768d1929f81de95ae6f 100644 (file)
@@ -88,6 +88,14 @@ ruleTester.run("no-func-assign", rule, {
                 data: { name: "foo" },
                 type: "Identifier"
             }]
+        },
+        {
+            code: "var a = function foo() { foo = 123; };",
+            errors: [{
+                messageId: "isAFunction",
+                data: { name: "foo" },
+                type: "Identifier"
+            }]
         }
     ]
 });
index 73bfe7df6d84e2598cea48baccf4b0bfa20f542c..fa2b68b4975977a3bcc8555180518ee6df12b332 100644 (file)
@@ -355,6 +355,28 @@ ruleTester.run("no-implicit-coercion", rule, {
                 data: { recommendation: "String(1n)" },
                 type: "BinaryExpression"
             }]
+        },
+
+        // Optional chaining
+        {
+            code: "~foo?.indexOf(1)",
+            output: null,
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{
+                messageId: "useRecommendation",
+                data: { recommendation: "foo?.indexOf(1) >= 0" },
+                type: "UnaryExpression"
+            }]
+        },
+        {
+            code: "~(foo?.indexOf)(1)",
+            output: null,
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{
+                messageId: "useRecommendation",
+                data: { recommendation: "(foo?.indexOf)(1) !== -1" },
+                type: "UnaryExpression"
+            }]
         }
     ]
 });
index 1c712f450f016b66688e562dd328a8a6689d96cd..ce06f6620434eb5f84172148562d60bc2f50b06a 100644 (file)
@@ -235,6 +235,20 @@ ruleTester.run("no-implied-eval", rule, {
                     line: 3
                 }
             ]
+        },
+
+        // Optional chaining
+        {
+            code: "window?.setTimeout('code', 0)",
+            parserOptions: { ecmaVersion: 2020 },
+            globals: { window: "readonly" },
+            errors: [{ messageId: "impliedEval" }]
+        },
+        {
+            code: "(window?.setTimeout)('code', 0)",
+            parserOptions: { ecmaVersion: 2020 },
+            globals: { window: "readonly" },
+            errors: [{ messageId: "impliedEval" }]
         }
     ]
 });
index ab65451e33af8a65947f423a301f62d17a755f70..babfdfc34455b9a14f96b6f393c3eee43dc855d6 100644 (file)
@@ -310,6 +310,23 @@ ruleTester.run("no-import-assign", rule, {
         {
             code: "import mod, * as mod_ns from 'mod'; mod.prop = 0; mod_ns.prop = 0",
             errors: [{ messageId: "readonlyMember", data: { name: "mod_ns" }, column: 51 }]
+        },
+
+        // Optional chaining
+        {
+            code: "import * as mod from 'mod'; Object?.defineProperty(mod, key, d)",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "readonlyMember", data: { name: "mod" }, column: 29 }]
+        },
+        {
+            code: "import * as mod from 'mod'; (Object?.defineProperty)(mod, key, d)",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "readonlyMember", data: { name: "mod" }, column: 29 }]
+        },
+        {
+            code: "import * as mod from 'mod'; delete mod?.prop",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "readonlyMember", data: { name: "mod" }, column: 29 }]
         }
     ]
 });
index ecb475997f7c5ad82b32db22ff14f6e34058f039..463c86eea660e448a73a0e32a23cc44bfe892c97 100644 (file)
@@ -88,7 +88,24 @@ ruleTester.run("no-inline-comments", rule, {
                comment
             */}
             </div>
-        )`
+        )`,
+        {
+            code: "import(/* webpackChunkName: \"my-chunk-name\" */ './locale/en');",
+            options: [
+                {
+                    ignorePattern: "(?:webpackChunkName):\\s.+"
+                }
+            ],
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "var foo = 2; // Note: This comment is legal.",
+            options: [
+                {
+                    ignorePattern: "Note: "
+                }
+            ]
+        }
     ],
 
     invalid: [
@@ -100,6 +117,15 @@ ruleTester.run("no-inline-comments", rule, {
             code: "/*A block comment inline before code*/ var a = 2;",
             errors: [blockError]
         },
+        {
+            code: "/* something */ var a = 2;",
+            options: [
+                {
+                    ignorePattern: "otherthing"
+                }
+            ],
+            errors: [blockError]
+        },
         {
             code: "var a = 3; //A comment inline with code",
             errors: [lineError]
@@ -108,6 +134,15 @@ ruleTester.run("no-inline-comments", rule, {
             code: "var a = 3; // someday use eslint-disable-line here",
             errors: [lineError]
         },
+        {
+            code: "var a = 3; // other line comment",
+            options: [
+                {
+                    ignorePattern: "something"
+                }
+            ],
+            errors: [lineError]
+        },
         {
             code: "var a = 4;\n/**A\n * block\n * comment\n * inline\n * between\n * code*/ var foo = a;",
             errors: [blockError]
index 3662a8367666bb9db2215243f33543369fe183a2..6e1b757a71237eae7d41cc34fbac06bada2c27f0 100644 (file)
@@ -366,6 +366,12 @@ const patterns = [
         invalid: [USE_STRICT, IMPLIED_STRICT, MODULES],
         errors
     },
+    {
+        code: "obj.foo = (function() { return function() { console.log(this); z(x => console.log(x, this)); }; })?.();",
+        parserOptions: { ecmaVersion: 2020 },
+        valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+        invalid: []
+    },
 
     // Class Instance Methods.
     {
@@ -421,6 +427,24 @@ const patterns = [
         valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
         invalid: []
     },
+    {
+        code: "var foo = function() { console.log(this); z(x => console.log(x, this)); }?.bind(obj);",
+        parserOptions: { ecmaVersion: 2020 },
+        valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+        invalid: []
+    },
+    {
+        code: "var foo = (function() { console.log(this); z(x => console.log(x, this)); }?.bind)(obj);",
+        parserOptions: { ecmaVersion: 2020 },
+        valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+        invalid: []
+    },
+    {
+        code: "var foo = function() { console.log(this); z(x => console.log(x, this)); }.bind?.(obj);",
+        parserOptions: { ecmaVersion: 2020 },
+        valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+        invalid: []
+    },
 
     // Array methods.
     {
@@ -534,6 +558,30 @@ const patterns = [
         valid: [NORMAL],
         invalid: [USE_STRICT, IMPLIED_STRICT, MODULES]
     },
+    {
+        code: "Array?.from([], function() { console.log(this); z(x => console.log(x, this)); }, obj);",
+        parserOptions: { ecmaVersion: 2020 },
+        valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+        invalid: []
+    },
+    {
+        code: "foo?.every(function() { console.log(this); z(x => console.log(x, this)); }, obj);",
+        parserOptions: { ecmaVersion: 2020 },
+        valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+        invalid: []
+    },
+    {
+        code: "(Array?.from)([], function() { console.log(this); z(x => console.log(x, this)); }, obj);",
+        parserOptions: { ecmaVersion: 2020 },
+        valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+        invalid: []
+    },
+    {
+        code: "(foo?.every)(function() { console.log(this); z(x => console.log(x, this)); }, obj);",
+        parserOptions: { ecmaVersion: 2020 },
+        valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+        invalid: []
+    },
 
     // @this tag.
     {
@@ -671,6 +719,24 @@ const patterns = [
         errors,
         valid: [NORMAL],
         invalid: [USE_STRICT, IMPLIED_STRICT, MODULES]
+    },
+    {
+        code: "obj.method &&= function () { console.log(this); z(x => console.log(x, this)); }",
+        parserOptions: { ecmaVersion: 2021 },
+        valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+        invalid: []
+    },
+    {
+        code: "obj.method ||= function () { console.log(this); z(x => console.log(x, this)); }",
+        parserOptions: { ecmaVersion: 2021 },
+        valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+        invalid: []
+    },
+    {
+        code: "obj.method ??= function () { console.log(this); z(x => console.log(x, this)); }",
+        parserOptions: { ecmaVersion: 2021 },
+        valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES],
+        invalid: []
     }
 ];
 
index 7852d5e5c27d2bd94fae594703b14a8bdf0af5eb..4055d360150f9a987427cfc4ccd075ec28040e83 100644 (file)
@@ -540,6 +540,335 @@ ruleTester.run("no-irregular-whitespace", rule, {
                     column: 14
                 }
             ]
+        },
+
+        // full location tests
+        {
+            code: "var foo = \u000B bar;",
+            errors: [
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 1,
+                    column: 11,
+                    endLine: 1,
+                    endColumn: 12
+                }
+            ]
+        },
+        {
+            code: "var foo =\u000Bbar;",
+            errors: [
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 1,
+                    column: 10,
+                    endLine: 1,
+                    endColumn: 11
+                }
+            ]
+        },
+        {
+            code: "var foo = \u000B\u000B bar;",
+            errors: [
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 1,
+                    column: 11,
+                    endLine: 1,
+                    endColumn: 13
+                }
+            ]
+        },
+        {
+            code: "var foo = \u000B\u000C bar;",
+            errors: [
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 1,
+                    column: 11,
+                    endLine: 1,
+                    endColumn: 13
+                }
+            ]
+        },
+        {
+            code: "var foo = \u000B \u000B bar;",
+            errors: [
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 1,
+                    column: 11,
+                    endLine: 1,
+                    endColumn: 12
+                },
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 1,
+                    column: 13,
+                    endLine: 1,
+                    endColumn: 14
+                }
+            ]
+        },
+        {
+            code: "var foo = \u000Bbar\u000B;",
+            errors: [
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 1,
+                    column: 11,
+                    endLine: 1,
+                    endColumn: 12
+                },
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 1,
+                    column: 15,
+                    endLine: 1,
+                    endColumn: 16
+                }
+            ]
+        },
+        {
+            code: "\u000B",
+            errors: [
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 1,
+                    column: 1,
+                    endLine: 1,
+                    endColumn: 2
+                }
+            ]
+        },
+        {
+            code: "\u00A0\u2002\u2003",
+            errors: [
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 1,
+                    column: 1,
+                    endLine: 1,
+                    endColumn: 4
+                }
+            ]
+        },
+        {
+            code: "var foo = \u000B\nbar;",
+            errors: [
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 1,
+                    column: 11,
+                    endLine: 1,
+                    endColumn: 12
+                }
+            ]
+        },
+        {
+            code: "var foo =\u000B\n\u000Bbar;",
+            errors: [
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 1,
+                    column: 10,
+                    endLine: 1,
+                    endColumn: 11
+                },
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 2,
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 2
+                }
+            ]
+        },
+        {
+            code: "var foo = \u000C\u000B\n\u000C\u000B\u000Cbar\n;\u000B\u000C\n",
+            errors: [
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 1,
+                    column: 11,
+                    endLine: 1,
+                    endColumn: 13
+                },
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 2,
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 4
+                },
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 3,
+                    column: 2,
+                    endLine: 3,
+                    endColumn: 4
+                }
+            ]
+        },
+        {
+            code: "var foo = \u2028bar;",
+            errors: [
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 1,
+                    column: 11,
+                    endLine: 2,
+                    endColumn: 1
+                }
+            ]
+        },
+        {
+            code: "var foo =\u2029 bar;",
+            errors: [
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 1,
+                    column: 10,
+                    endLine: 2,
+                    endColumn: 1
+                }
+            ]
+        },
+        {
+            code: "var foo = bar;\u2028",
+            errors: [
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 1,
+                    column: 15,
+                    endLine: 2,
+                    endColumn: 1
+                }
+            ]
+        },
+        {
+            code: "\u2029",
+            errors: [
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 1,
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 1
+                }
+            ]
+        },
+        {
+            code: "foo\u2028\u2028",
+            errors: [
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 1,
+                    column: 4,
+                    endLine: 2,
+                    endColumn: 1
+                },
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 2,
+                    column: 1,
+                    endLine: 3,
+                    endColumn: 1
+                }
+            ]
+        },
+        {
+            code: "foo\u2029\u2028",
+            errors: [
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 1,
+                    column: 4,
+                    endLine: 2,
+                    endColumn: 1
+                },
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 2,
+                    column: 1,
+                    endLine: 3,
+                    endColumn: 1
+                }
+            ]
+        },
+        {
+            code: "foo\u2028\n\u2028",
+            errors: [
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 1,
+                    column: 4,
+                    endLine: 2,
+                    endColumn: 1
+                },
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 3,
+                    column: 1,
+                    endLine: 4,
+                    endColumn: 1
+                }
+            ]
+        },
+        {
+            code: "foo\u000B\u2028\u000B",
+            errors: [
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 1,
+                    column: 4,
+                    endLine: 1,
+                    endColumn: 5
+                },
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 1,
+                    column: 5,
+                    endLine: 2,
+                    endColumn: 1
+                },
+                {
+                    messageId: "noIrregularWhitespace",
+                    type: "Program",
+                    line: 2,
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 2
+                }
+            ]
         }
     ]
 });
index fd2fb2045249eaad2a153fdaae191ec124cc06b8..c690dd7e1bc45cdd0a0f445e4f0660d6fe4d8e18 100644 (file)
@@ -47,14 +47,33 @@ ruleTester.run("no-loss-of-precision", rule, {
         "var x = 0195",
         "var x = 0e5",
 
+        { code: "var x = 12_34_56", parserOptions: { ecmaVersion: 2021 } },
+        { code: "var x = 12_3.4_56", parserOptions: { ecmaVersion: 2021 } },
+        { code: "var x = -12_3.4_56", parserOptions: { ecmaVersion: 2021 } },
+        { code: "var x = -12_34_56", parserOptions: { ecmaVersion: 2021 } },
+        { code: "var x = 12_3e3_4", parserOptions: { ecmaVersion: 2021 } },
+        { code: "var x = 123.0e3_4", parserOptions: { ecmaVersion: 2021 } },
+        { code: "var x = 12_3e-3_4", parserOptions: { ecmaVersion: 2021 } },
+        { code: "var x = 12_3.0e-3_4", parserOptions: { ecmaVersion: 2021 } },
+        { code: "var x = -1_23e-3_4", parserOptions: { ecmaVersion: 2021 } },
+        { code: "var x = -1_23.8e-3_4", parserOptions: { ecmaVersion: 2021 } },
+        { code: "var x = 1_230000000_00000000_00000_000", parserOptions: { ecmaVersion: 2021 } },
+        { code: "var x = -1_230000000_00000000_00000_000", parserOptions: { ecmaVersion: 2021 } },
+        { code: "var x = 0.0_00_000000000_000000000_00123", parserOptions: { ecmaVersion: 2021 } },
+        { code: "var x = -0.0_00_000000000_000000000_00123", parserOptions: { ecmaVersion: 2021 } },
+        { code: "var x = 0e5_3", parserOptions: { ecmaVersion: 2021 } },
 
         { code: "var x = 0b11111111111111111111111111111111111111111111111111111", parserOptions: { ecmaVersion: 6 } },
+        { code: "var x = 0b111_111_111_111_1111_11111_111_11111_1111111111_11111111_111_111", parserOptions: { ecmaVersion: 2021 } },
+
         { code: "var x = 0B11111111111111111111111111111111111111111111111111111", parserOptions: { ecmaVersion: 6 } },
+        { code: "var x = 0B111_111_111_111_1111_11111_111_11111_1111111111_11111111_111_111", parserOptions: { ecmaVersion: 2021 } },
 
         { code: "var x = 0o377777777777777777", parserOptions: { ecmaVersion: 6 } },
+        { code: "var x = 0o3_77_777_777_777_777_777", parserOptions: { ecmaVersion: 2021 } },
         { code: "var x = 0O377777777777777777", parserOptions: { ecmaVersion: 6 } },
-        "var x = 0377777777777777777",
 
+        "var x = 0377777777777777777",
         "var x = 0x1FFFFFFFFFFFFF",
         "var x = 0X1FFFFFFFFFFFFF",
         "var x = true",
@@ -65,8 +84,10 @@ ruleTester.run("no-loss-of-precision", rule, {
         "var x = {}",
         "var x = ['a', 'b']",
         "var x = new Date()",
-        "var x = '9007199254740993'"
+        "var x = '9007199254740993'",
 
+        { code: "var x = 0x1FFF_FFFF_FFF_FFF", parserOptions: { ecmaVersion: 2021 } },
+        { code: "var x = 0X1_FFF_FFFF_FFF_FFF", parserOptions: { ecmaVersion: 2021 } }
     ],
     invalid: [
         {
@@ -93,7 +114,36 @@ ruleTester.run("no-loss-of-precision", rule, {
             code: "var x = -900719.9254740994",
             errors: [{ messageId: "noLossOfPrecision" }]
         },
-
+        {
+            code: "var x = 900719925474099_3",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "noLossOfPrecision" }]
+        },
+        {
+            code: "var x = 90_0719925_4740.9_93e3",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "noLossOfPrecision" }]
+        },
+        {
+            code: "var x = 9.0_0719925_474099_3e15",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "noLossOfPrecision" }]
+        },
+        {
+            code: "var x = -9_00719_9254_740993",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "noLossOfPrecision" }]
+        },
+        {
+            code: "var x = 900_719.92_54740_994",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "noLossOfPrecision" }]
+        },
+        {
+            code: "var x = -900_719.92_5474_0994",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "noLossOfPrecision" }]
+        },
         {
             code: "var x = 5123000000000000000000000000001",
             errors: [{ messageId: "noLossOfPrecision" }]
@@ -153,7 +203,71 @@ ruleTester.run("no-loss-of-precision", rule, {
         {
             code: "var x = 0X20000000000001",
             errors: [{ messageId: "noLossOfPrecision" }]
+        },
+        {
+            code: "var x = 5123_00000000000000000000000000_1",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "noLossOfPrecision" }]
+        },
+        {
+            code: "var x = -5_12300000000000000000000_0000001",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "noLossOfPrecision" }]
+        },
+        {
+            code: "var x = 123_00000000000000000000_00.0_0",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "noLossOfPrecision" }]
+        },
+        {
+            code: "var x = 1.0_00000000000000000_0000123",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "noLossOfPrecision" }]
+        },
+        {
+            code: "var x = 174_980057982_640953949800178169_409709228253554471456994_914061648512796239935950073857881054_1618443059_2",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "noLossOfPrecision" }]
+        },
+        {
+            code: "var x = 2e9_99",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "noLossOfPrecision" }]
+        },
+        {
+            code: "var x = .1_23000000000000_00000_0000_0",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "noLossOfPrecision" }]
+        },
+        {
+            code: "var x = 0b1_0000000000000000000000000000000000000000000000000000_1",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "noLossOfPrecision" }]
+        },
+        {
+            code: "var x = 0B10000000000_0000000000000000000000000000_000000000000001",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "noLossOfPrecision" }]
+        },
+        {
+            code: "var x = 0o4_00000000000000_001",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "noLossOfPrecision" }]
+        },
+        {
+            code: "var x = 0O4_0000000000000000_1",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "noLossOfPrecision" }]
+        },
+        {
+            code: "var x = 0x2_0000000000001",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "noLossOfPrecision" }]
+        },
+        {
+            code: "var x = 0X200000_0000000_1",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "noLossOfPrecision" }]
         }
-
     ]
 });
index da75571ec28dc10b4169aface98d5f69f2763750..bbdf8ca984f9615c430d169d7ad770d392f4be1e 100644 (file)
@@ -213,6 +213,50 @@ ruleTester.run("no-magic-numbers", rule, {
             code: "f(-100n)",
             options: [{ ignore: ["-100n"] }],
             parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "const { param = 123 } = sourceObject;",
+            options: [{ ignoreDefaultValues: true }],
+            env: { es6: true }
+        },
+        {
+            code: "const func = (param = 123) => {}",
+            options: [{ ignoreDefaultValues: true }],
+            env: { es6: true }
+        },
+        {
+            code: "const func = ({ param = 123 }) => {}",
+            options: [{ ignoreDefaultValues: true }],
+            env: { es6: true }
+        },
+        {
+            code: "const [one = 1, two = 2] = []",
+            options: [{ ignoreDefaultValues: true }],
+            env: { es6: true }
+        },
+        {
+            code: "var one, two; [one = 1, two = 2] = []",
+            options: [{ ignoreDefaultValues: true }],
+            env: { es6: true }
+        },
+
+        // Optional chaining
+        {
+            code: "var x = parseInt?.(y, 10);",
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "var x = Number?.parseInt(y, 10);",
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "var x = (Number?.parseInt)(y, 10);",
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "foo?.[777]",
+            options: [{ ignoreArrayIndexes: true }],
+            parserOptions: { ecmaVersion: 2020 }
         }
     ],
     invalid: [
@@ -719,6 +763,55 @@ ruleTester.run("no-magic-numbers", rule, {
             errors: [
                 { messageId: "noMagic", data: { raw: "100" }, line: 1 }
             ]
+        },
+        {
+            code: "const func = (param = 123) => {}",
+            options: [{ ignoreDefaultValues: false }],
+            env: { es6: true },
+            errors: [
+                { messageId: "noMagic", data: { raw: "123" }, line: 1 }
+            ]
+        },
+        {
+            code: "const { param = 123 } = sourceObject;",
+            options: [{}],
+            env: { es6: true },
+            errors: [
+                { messageId: "noMagic", data: { raw: "123" }, line: 1 }
+            ]
+        },
+        {
+            code: "const { param = 123 } = sourceObject;",
+            env: { es6: true },
+            errors: [
+                { messageId: "noMagic", data: { raw: "123" }, line: 1 }
+            ]
+        },
+        {
+            code: "const { param = 123 } = sourceObject;",
+            options: [{ ignoreDefaultValues: false }],
+            env: { es6: true },
+            errors: [
+                { messageId: "noMagic", data: { raw: "123" }, line: 1 }
+            ]
+        },
+        {
+            code: "const [one = 1, two = 2] = []",
+            options: [{ ignoreDefaultValues: false }],
+            env: { es6: true },
+            errors: [
+                { messageId: "noMagic", data: { raw: "1" }, line: 1 },
+                { messageId: "noMagic", data: { raw: "2" }, line: 1 }
+            ]
+        },
+        {
+            code: "var one, two; [one = 1, two = 2] = []",
+            options: [{ ignoreDefaultValues: false }],
+            env: { es6: true },
+            errors: [
+                { messageId: "noMagic", data: { raw: "1" }, line: 1 },
+                { messageId: "noMagic", data: { raw: "2" }, line: 1 }
+            ]
         }
     ]
 });
index 4c21d9be4c86fc39cb8692aa3c43ea07eef99ca9..6270e7a728040d4ca959be95d574ec08c9b4f3e2 100644 (file)
@@ -315,6 +315,18 @@ ruleTester.run("no-obj-calls", rule, {
             code: "var foo = window.Atomics; new foo;",
             env: { es2020: true, browser: true },
             errors: [{ messageId: "unexpectedRefCall", data: { name: "foo", ref: "Atomics" }, type: "NewExpression" }]
+        },
+
+        // Optional chaining
+        {
+            code: "var x = globalThis?.Reflect();",
+            env: { es2020: true },
+            errors: [{ messageId: "unexpectedCall", data: { name: "Reflect" }, type: "CallExpression" }]
+        },
+        {
+            code: "var x = (globalThis?.Reflect)();",
+            env: { es2020: true },
+            errors: [{ messageId: "unexpectedCall", data: { name: "Reflect" }, type: "CallExpression" }]
         }
     ]
 });
index a79249d1ef6280ffd426408e175f97f4b4f023f9..5f521cbc3cd33f48bcb5a3d19ac0455c0825d52c 100644 (file)
@@ -368,6 +368,57 @@ ruleTester.run("no-param-reassign", rule, {
                 messageId: "assignmentToFunctionParamProp",
                 data: { name: "a" }
             }]
+        },
+        {
+            code: "function foo(a) { a &&= b; }",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{
+                messageId: "assignmentToFunctionParam",
+                data: { name: "a" }
+            }]
+        },
+        {
+            code: "function foo(a) { a ||= b; }",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{
+                messageId: "assignmentToFunctionParam",
+                data: { name: "a" }
+            }]
+        },
+        {
+            code: "function foo(a) { a ??= b; }",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{
+                messageId: "assignmentToFunctionParam",
+                data: { name: "a" }
+            }]
+        },
+        {
+            code: "function foo(a) { a.b &&= c; }",
+            options: [{ props: true }],
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{
+                messageId: "assignmentToFunctionParamProp",
+                data: { name: "a" }
+            }]
+        },
+        {
+            code: "function foo(a) { a.b.c ||= d; }",
+            options: [{ props: true }],
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{
+                messageId: "assignmentToFunctionParamProp",
+                data: { name: "a" }
+            }]
+        },
+        {
+            code: "function foo(a) { a[b] ??= c; }",
+            options: [{ props: true }],
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{
+                messageId: "assignmentToFunctionParamProp",
+                data: { name: "a" }
+            }]
         }
     ]
 });
diff --git a/eslint/tests/lib/rules/no-promise-executor-return.js b/eslint/tests/lib/rules/no-promise-executor-return.js
new file mode 100644 (file)
index 0000000..a24629b
--- /dev/null
@@ -0,0 +1,303 @@
+/**
+ * @fileoverview Tests for the no-promise-executor-return rule
+ * @author Milos Djermanovic
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const rule = require("../../../lib/rules/no-promise-executor-return");
+const { RuleTester } = require("../../../lib/rule-tester");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Creates an error object.
+ * @param {number} [column] Reported column.
+ * @param {string} [type="ReturnStatement"] Reported node type.
+ * @returns {Object} The error object.
+ */
+function error(column, type = "ReturnStatement") {
+    const errorObject = {
+        messageId: "returnsValue",
+        type
+    };
+
+    if (column) {
+        errorObject.column = column;
+    }
+
+    return errorObject;
+}
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015 }, env: { es6: true } });
+
+ruleTester.run("no-promise-executor-return", rule, {
+    valid: [
+
+        //------------------------------------------------------------------------------
+        // General
+        //------------------------------------------------------------------------------
+
+        // not a promise executor
+        "function foo(resolve, reject) { return 1; }",
+        "function Promise(resolve, reject) { return 1; }",
+        "(function (resolve, reject) { return 1; })",
+        "(function foo(resolve, reject) { return 1; })",
+        "(function Promise(resolve, reject) { return 1; })",
+        "var foo = function (resolve, reject) { return 1; }",
+        "var foo = function Promise(resolve, reject) { return 1; }",
+        "var Promise = function (resolve, reject) { return 1; }",
+        "(resolve, reject) => { return 1; }",
+        "(resolve, reject) => 1",
+        "var foo = (resolve, reject) => { return 1; }",
+        "var Promise = (resolve, reject) => { return 1; }",
+        "var foo = (resolve, reject) => 1",
+        "var Promise = (resolve, reject) => 1",
+        "var foo = { bar(resolve, reject) { return 1; } }",
+        "var foo = { Promise(resolve, reject) { return 1; } }",
+        "new foo(function (resolve, reject) { return 1; });",
+        "new foo(function bar(resolve, reject) { return 1; });",
+        "new foo(function Promise(resolve, reject) { return 1; });",
+        "new foo((resolve, reject) => { return 1; });",
+        "new foo((resolve, reject) => 1);",
+        "new promise(function foo(resolve, reject) { return 1; });",
+        "new Promise.foo(function foo(resolve, reject) { return 1; });",
+        "new foo.Promise(function foo(resolve, reject) { return 1; });",
+        "new Promise.Promise(function foo(resolve, reject) { return 1; });",
+        "new Promise()(function foo(resolve, reject) { return 1; });",
+
+        // not a promise executor - Promise() without new
+        "Promise(function (resolve, reject) { return 1; });",
+        "Promise((resolve, reject) => { return 1; });",
+        "Promise((resolve, reject) => 1);",
+
+        // not a promise executor - not the first argument
+        "new Promise(foo, function (resolve, reject) { return 1; });",
+        "new Promise(foo, (resolve, reject) => { return 1; });",
+        "new Promise(foo, (resolve, reject) => 1);",
+
+        // global Promise doesn't exist
+        "/* globals Promise:off */ new Promise(function (resolve, reject) { return 1; });",
+        {
+            code: "new Promise((resolve, reject) => { return 1; });",
+            globals: { Promise: "off" }
+        },
+        {
+            code: "new Promise((resolve, reject) => 1);",
+            env: { es6: false }
+        },
+
+        // global Promise is shadowed
+        "let Promise; new Promise(function (resolve, reject) { return 1; });",
+        "function f() { new Promise((resolve, reject) => { return 1; }); var Promise; }",
+        "function f(Promise) { new Promise((resolve, reject) => 1); }",
+        "if (x) { const Promise = foo(); new Promise(function (resolve, reject) { return 1; }); }",
+        "x = function Promise() { new Promise((resolve, reject) => { return 1; }); }",
+
+        // return without a value is allowed
+        "new Promise(function (resolve, reject) { return; });",
+        "new Promise(function (resolve, reject) { reject(new Error()); return; });",
+        "new Promise(function (resolve, reject) { if (foo) { return; } });",
+        "new Promise((resolve, reject) => { return; });",
+        "new Promise((resolve, reject) => { if (foo) { resolve(1); return; } reject(new Error()); });",
+
+        // throw is allowed
+        "new Promise(function (resolve, reject) { throw new Error(); });",
+        "new Promise((resolve, reject) => { throw new Error(); });",
+
+        // not returning from the promise executor
+        "new Promise(function (resolve, reject) { function foo() { return 1; } });",
+        "new Promise((resolve, reject) => { (function foo() { return 1; })(); });",
+        "new Promise(function (resolve, reject) { () => { return 1; } });",
+        "new Promise((resolve, reject) => { () => 1 });",
+        "function foo() { return new Promise(function (resolve, reject) { resolve(bar); }) };",
+        "foo => new Promise((resolve, reject) => { bar(foo, (err, data) => { if (err) { reject(err); return; } resolve(data); })});",
+
+        // promise executors do not have effect on other functions (tests function info tracking)
+        "new Promise(function (resolve, reject) {}); function foo() { return 1; }",
+        "new Promise((resolve, reject) => {}); (function () { return 1; });",
+        "new Promise(function (resolve, reject) {}); () => { return 1; };",
+        "new Promise((resolve, reject) => {}); () => 1;",
+
+        // does not report global return
+        {
+            code: "return 1;",
+            env: { node: true }
+        },
+        {
+            code: "return 1;",
+            parserOptions: { ecmaFeatures: { globalReturn: true } }
+        },
+        {
+            code: "return 1; function foo(){ return 1; } return 1;",
+            env: { node: true }
+        },
+        {
+            code: "function foo(){} return 1; var bar = function*(){ return 1; }; return 1; var baz = () => {}; return 1;",
+            env: { node: true }
+        },
+        {
+            code: "new Promise(function (resolve, reject) {}); return 1;",
+            env: { node: true }
+        }
+    ],
+
+    invalid: [
+
+        // full error tests
+        {
+            code: "new Promise(function (resolve, reject) { return 1; })",
+            errors: [{ message: "Return values from promise executor functions cannot be read.", type: "ReturnStatement", column: 42, endColumn: 51 }]
+        },
+        {
+            code: "new Promise((resolve, reject) => resolve(1))",
+            errors: [{ message: "Return values from promise executor functions cannot be read.", type: "CallExpression", column: 34, endColumn: 44 }]
+        },
+
+        // other basic tests
+        {
+            code: "new Promise(function foo(resolve, reject) { return 1; })",
+            errors: [error()]
+        },
+        {
+            code: "new Promise((resolve, reject) => { return 1; })",
+            errors: [error()]
+        },
+
+        // any returned value
+        {
+            code: "new Promise(function (resolve, reject) { return undefined; })",
+            errors: [error()]
+        },
+        {
+            code: "new Promise((resolve, reject) => { return null; })",
+            errors: [error()]
+        },
+        {
+            code: "new Promise(function (resolve, reject) { return false; })",
+            errors: [error()]
+        },
+        {
+            code: "new Promise((resolve, reject) => resolve)",
+            errors: [error(34, "Identifier")]
+        },
+        {
+            code: "new Promise((resolve, reject) => null)",
+            errors: [error(34, "Literal")]
+        },
+        {
+            code: "new Promise(function (resolve, reject) { return resolve(foo); })",
+            errors: [error()]
+        },
+        {
+            code: "new Promise((resolve, reject) => { return reject(foo); })",
+            errors: [error()]
+        },
+        {
+            code: "new Promise((resolve, reject) => x + y)",
+            errors: [error(34, "BinaryExpression")]
+        },
+        {
+            code: "new Promise((resolve, reject) => { return Promise.resolve(42); })",
+            errors: [error()]
+        },
+
+        // any return statement location
+        {
+            code: "new Promise(function (resolve, reject) { if (foo) { return 1; } })",
+            errors: [error()]
+        },
+        {
+            code: "new Promise((resolve, reject) => { try { return 1; } catch(e) {} })",
+            errors: [error()]
+        },
+        {
+            code: "new Promise(function (resolve, reject) { while (foo){ if (bar) break; else return 1; } })",
+            errors: [error()]
+        },
+
+        // absence of arguments has no effect
+        {
+            code: "new Promise(function () { return 1; })",
+            errors: [error()]
+        },
+        {
+            code: "new Promise(() => { return 1; })",
+            errors: [error()]
+        },
+        {
+            code: "new Promise(() => 1)",
+            errors: [error(19, "Literal")]
+        },
+
+        // various scope tracking tests
+        {
+            code: "function foo() {} new Promise(function () { return 1; });",
+            errors: [error(45)]
+        },
+        {
+            code: "function foo() { return; } new Promise(() => { return 1; });",
+            errors: [error(48)]
+        },
+        {
+            code: "function foo() { return 1; } new Promise(() => { return 2; });",
+            errors: [error(50)]
+        },
+        {
+            code: "function foo () { return new Promise(function () { return 1; }); }",
+            errors: [error(52)]
+        },
+        {
+            code: "function foo() { return new Promise(() => { bar(() => { return 1; }); return false; }); }",
+            errors: [error(71)]
+        },
+        {
+            code: "() => new Promise(() => { if (foo) { return 0; } else bar(() => { return 1; }); })",
+            errors: [error(38)]
+        },
+        {
+            code: "function foo () { return 1; return new Promise(function () { return 2; }); return 3;}",
+            errors: [error(62)]
+        },
+        {
+            code: "() => 1; new Promise(() => { return 1; })",
+            errors: [error(30)]
+        },
+        {
+            code: "new Promise(function () { return 1; }); function foo() { return 1; } ",
+            errors: [error(27)]
+        },
+        {
+            code: "() => new Promise(() => { return 1; });",
+            errors: [error(27)]
+        },
+        {
+            code: "() => new Promise(() => 1);",
+            errors: [error(25, "Literal")]
+        },
+        {
+            code: "() => new Promise(() => () => 1);",
+            errors: [error(25, "ArrowFunctionExpression")]
+        },
+
+        // edge cases for global Promise reference
+        {
+            code: "new Promise((Promise) => { return 1; })",
+            errors: [error()]
+        },
+        {
+            code: "new Promise(function Promise(resolve, reject) { return 1; })",
+            errors: [error()]
+        }
+    ]
+});
index e4f0fa30f78889f1742883b4b9ecf6f05cead801..a65b54d4d63680b948a37d5dc4628a71d2a46809 100644 (file)
@@ -17,87 +17,156 @@ const rule = require("../../../lib/rules/no-prototype-builtins"),
 //------------------------------------------------------------------------------
 const ruleTester = new RuleTester();
 
-const valid = [
-    { code: "Object.prototype.hasOwnProperty.call(foo, 'bar')" },
-    { code: "Object.prototype.isPrototypeOf.call(foo, 'bar')" },
-    { code: "Object.prototype.propertyIsEnumerable.call(foo, 'bar')" },
-    { code: "Object.prototype.hasOwnProperty.apply(foo, ['bar'])" },
-    { code: "Object.prototype.isPrototypeOf.apply(foo, ['bar'])" },
-    { code: "Object.prototype.propertyIsEnumerable.apply(foo, ['bar'])" },
-    { code: "hasOwnProperty(foo, 'bar')" },
-    { code: "isPrototypeOf(foo, 'bar')" },
-    { code: "propertyIsEnumerable(foo, 'bar')" },
-    { code: "({}.hasOwnProperty.call(foo, 'bar'))" },
-    { code: "({}.isPrototypeOf.call(foo, 'bar'))" },
-    { code: "({}.propertyIsEnumerable.call(foo, 'bar'))" },
-    { code: "({}.hasOwnProperty.apply(foo, ['bar']))" },
-    { code: "({}.isPrototypeOf.apply(foo, ['bar']))" },
-    { code: "({}.propertyIsEnumerable.apply(foo, ['bar']))" }
-];
+ruleTester.run("no-prototype-builtins", rule, {
+    valid: [
+        "Object.prototype.hasOwnProperty.call(foo, 'bar')",
+        "Object.prototype.isPrototypeOf.call(foo, 'bar')",
+        "Object.prototype.propertyIsEnumerable.call(foo, 'bar')",
+        "Object.prototype.hasOwnProperty.apply(foo, ['bar'])",
+        "Object.prototype.isPrototypeOf.apply(foo, ['bar'])",
+        "Object.prototype.propertyIsEnumerable.apply(foo, ['bar'])",
+        "foo.hasOwnProperty",
+        "foo.hasOwnProperty.bar()",
+        "foo(hasOwnProperty)",
+        "hasOwnProperty(foo, 'bar')",
+        "isPrototypeOf(foo, 'bar')",
+        "propertyIsEnumerable(foo, 'bar')",
+        "({}.hasOwnProperty.call(foo, 'bar'))",
+        "({}.isPrototypeOf.call(foo, 'bar'))",
+        "({}.propertyIsEnumerable.call(foo, 'bar'))",
+        "({}.hasOwnProperty.apply(foo, ['bar']))",
+        "({}.isPrototypeOf.apply(foo, ['bar']))",
+        "({}.propertyIsEnumerable.apply(foo, ['bar']))",
+        "foo[hasOwnProperty]('bar')",
+        "foo['HasOwnProperty']('bar')",
+        { code: "foo[`isPrototypeOff`]('bar')", parserOptions: { ecmaVersion: 2015 } },
+        { code: "foo?.['propertyIsEnumerabl']('bar')", parserOptions: { ecmaVersion: 2020 } },
+        "foo[1]('bar')",
+        "foo[null]('bar')",
 
-const invalid = [
-    {
-        code: "foo.hasOwnProperty('bar')",
-        errors: [{
-            line: 1,
-            column: 5,
-            endLine: 1,
-            endColumn: 19,
-            messageId: "prototypeBuildIn",
-            data: { prop: "hasOwnProperty" },
-            type: "CallExpression"
-        }]
-    },
-    {
-        code: "foo.isPrototypeOf('bar')",
-        errors: [{
-            line: 1,
-            column: 5,
-            endLine: 1,
-            endColumn: 18,
-            messageId: "prototypeBuildIn",
-            data: { prop: "isPrototypeOf" },
-            type: "CallExpression"
-        }]
-    },
-    {
-        code: "foo.propertyIsEnumerable('bar')",
-        errors: [{
-            line: 1,
-            column: 5,
-            endLine: 1,
-            endColumn: 25,
-            messageId: "prototypeBuildIn",
-            data: { prop: "propertyIsEnumerable" }
-        }]
-    },
-    {
-        code: "foo.bar.hasOwnProperty('bar')",
-        errors: [{
-            line: 1,
-            column: 9,
-            endLine: 1,
-            endColumn: 23,
-            messageId: "prototypeBuildIn",
-            data: { prop: "hasOwnProperty" },
-            type: "CallExpression"
-        }]
-    },
-    {
-        code: "foo.bar.baz.isPrototypeOf('bar')",
-        errors: [{
-            line: 1,
-            column: 13,
-            endLine: 1,
-            endColumn: 26,
-            messageId: "prototypeBuildIn",
-            data: { prop: "isPrototypeOf" },
-            type: "CallExpression"
-        }]
-    }
-];
+        // out of scope for this rule
+        "foo['hasOwn' + 'Property']('bar')",
+        { code: "foo[`hasOwnProperty${''}`]('bar')", parserOptions: { ecmaVersion: 2015 } }
+    ],
 
-ruleTester.run("no-prototype-builtins", rule, {
-    valid,
-    invalid
+    invalid: [
+        {
+            code: "foo.hasOwnProperty('bar')",
+            errors: [{
+                line: 1,
+                column: 5,
+                endLine: 1,
+                endColumn: 19,
+                messageId: "prototypeBuildIn",
+                data: { prop: "hasOwnProperty" },
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "foo.isPrototypeOf('bar')",
+            errors: [{
+                line: 1,
+                column: 5,
+                endLine: 1,
+                endColumn: 18,
+                messageId: "prototypeBuildIn",
+                data: { prop: "isPrototypeOf" },
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "foo.propertyIsEnumerable('bar')",
+            errors: [{
+                line: 1,
+                column: 5,
+                endLine: 1,
+                endColumn: 25,
+                messageId: "prototypeBuildIn",
+                data: { prop: "propertyIsEnumerable" }
+            }]
+        },
+        {
+            code: "foo.bar.hasOwnProperty('bar')",
+            errors: [{
+                line: 1,
+                column: 9,
+                endLine: 1,
+                endColumn: 23,
+                messageId: "prototypeBuildIn",
+                data: { prop: "hasOwnProperty" },
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "foo.bar.baz.isPrototypeOf('bar')",
+            errors: [{
+                line: 1,
+                column: 13,
+                endLine: 1,
+                endColumn: 26,
+                messageId: "prototypeBuildIn",
+                data: { prop: "isPrototypeOf" },
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "foo['hasOwnProperty']('bar')",
+            errors: [{
+                line: 1,
+                column: 5,
+                endLine: 1,
+                endColumn: 21,
+                messageId: "prototypeBuildIn",
+                data: { prop: "hasOwnProperty" },
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "foo[`isPrototypeOf`]('bar').baz",
+            parserOptions: { ecmaVersion: 2015 },
+            errors: [{
+                line: 1,
+                column: 5,
+                endLine: 1,
+                endColumn: 20,
+                messageId: "prototypeBuildIn",
+                data: { prop: "isPrototypeOf" },
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: String.raw`foo.bar["propertyIsEnumerable"]('baz')`,
+            errors: [{
+                line: 1,
+                column: 9,
+                endLine: 1,
+                endColumn: 31,
+                messageId: "prototypeBuildIn",
+                data: { prop: "propertyIsEnumerable" },
+                type: "CallExpression"
+            }]
+        },
+
+        // Optional chaining
+        {
+            code: "foo?.hasOwnProperty('bar')",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "prototypeBuildIn", data: { prop: "hasOwnProperty" } }]
+        },
+        {
+            code: "(foo?.hasOwnProperty)('bar')",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "prototypeBuildIn", data: { prop: "hasOwnProperty" } }]
+        },
+        {
+            code: "foo?.['hasOwnProperty']('bar')",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "prototypeBuildIn", data: { prop: "hasOwnProperty" } }]
+        },
+        {
+            code: "(foo?.[`hasOwnProperty`])('bar')",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "prototypeBuildIn", data: { prop: "hasOwnProperty" } }]
+        }
+    ]
 });
index c8ba48fb8ec1f15e9b8b10aee62f227b32768d70..cf8bc41236644d7c46433c5e154a87af085cc949 100644 (file)
@@ -128,6 +128,35 @@ ruleTester.run("no-restricted-syntax", rule, {
             code: "console.log(/a/i);",
             options: ["Literal[regex.flags=/./]"],
             errors: [{ messageId: "restrictedSyntax", data: { message: "Using 'Literal[regex.flags=/./]' is not allowed." }, type: "Literal" }]
+        },
+
+        // Optional chaining
+        {
+            code: "var foo = foo?.bar?.();",
+            options: ["ChainExpression"],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "restrictedSyntax", data: { message: "Using 'ChainExpression' is not allowed." }, type: "ChainExpression" }]
+        },
+        {
+            code: "var foo = foo?.bar?.();",
+            options: ["[optional=true]"],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [
+                { messageId: "restrictedSyntax", data: { message: "Using '[optional=true]' is not allowed." }, type: "CallExpression" },
+                { messageId: "restrictedSyntax", data: { message: "Using '[optional=true]' is not allowed." }, type: "MemberExpression" }
+            ]
         }
+
+        /*
+         * 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" }
+         *     ]
+         * }
+         */
     ]
 });
index 6eca268ce8dfb99e752ef517d533caf6ea38dfe7..8dd296161a88b3a9c81bbe522f63bbbdc9a4cbf7 100644 (file)
@@ -22,7 +22,19 @@ ruleTester.run("no-script-url", rule, {
     valid: [
         "var a = 'Hello World!';",
         "var a = 10;",
-        "var url = 'xjavascript:'"
+        "var url = 'xjavascript:'",
+        {
+            code: "var url = `xjavascript:`",
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "var url = `${foo}javascript:`",
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "var a = foo`javaScript:`;",
+            parserOptions: { ecmaVersion: 6 }
+        }
     ],
     invalid: [
         {
@@ -36,6 +48,20 @@ ruleTester.run("no-script-url", rule, {
             errors: [
                 { messageId: "unexpectedScriptURL", type: "Literal" }
             ]
+        },
+        {
+            code: "var a = `javascript:`;",
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                { messageId: "unexpectedScriptURL", type: "TemplateLiteral" }
+            ]
+        },
+        {
+            code: "var a = `JavaScript:`;",
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                { messageId: "unexpectedScriptURL", type: "TemplateLiteral" }
+            ]
         }
     ]
 });
index 5149dac65b2bfd530dd11c20640305da1f046a3a..5a9bb6fcfed6926690f70afcf6e69b526119be27 100644 (file)
@@ -135,6 +135,18 @@ ruleTester.run("no-self-assign", rule, {
             options: [{ props: true }],
             errors: [{ messageId: "selfAssignment", data: { name: "this.x" } }]
         },
-        { code: "a['/(?<zero>0)/'] = a[/(?<zero>0)/]", options: [{ props: true }], parserOptions: { ecmaVersion: 2018 }, errors: [{ messageId: "selfAssignment", data: { name: "a[/(?<zero>0)/]" } }] }
+        { code: "a['/(?<zero>0)/'] = a[/(?<zero>0)/]", options: [{ props: true }], parserOptions: { ecmaVersion: 2018 }, errors: [{ messageId: "selfAssignment", data: { name: "a[/(?<zero>0)/]" } }] },
+
+        // Optional chaining
+        {
+            code: "(a?.b).c = (a?.b).c",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "selfAssignment", data: { name: "(a?.b).c" } }]
+        },
+        {
+            code: "a.b = a?.b",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "selfAssignment", data: { name: "a?.b" } }]
+        }
     ]
 });
index b70b4e3f1c06077ba644ebf12a172c04afa09dd3..0c64e8b48110d79c97516f3065dd09fda0457f63 100644 (file)
@@ -39,7 +39,7 @@ function error(column, type = "ReturnStatement") {
 // Tests
 //------------------------------------------------------------------------------
 
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } });
 
 ruleTester.run("no-setter-return", rule, {
     valid: [
@@ -505,6 +505,18 @@ ruleTester.run("no-setter-return", rule, {
         {
             code: "Object.defineProperty(foo, 'bar', { set: function(Object) { return 1; } })",
             errors: [error()]
+        },
+
+        // Optional chaining
+        {
+            code: "Object?.defineProperty(foo, 'bar', { set(val) { return 1; } })",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [error()]
+        },
+        {
+            code: "(Object?.defineProperty)(foo, 'bar', { set(val) { return 1; } })",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [error()]
         }
     ]
 });
index c41b8c9c179099ad70eee67420d36fa1886c59aa..7a947c00b1cf5ceaa5c43bb4984bbd03f61d84e0 100644 (file)
@@ -39,6 +39,10 @@ ruleTester.run("no-this-before-super", rule, {
         "class A extends B { constructor() { super(); this.c(); } }",
         "class A extends B { constructor() { super(); super.c(); } }",
         "class A extends B { constructor() { if (true) { super(); } else { super(); } this.c(); } }",
+        "class A extends B { constructor() { foo = super(); this.c(); } }",
+        "class A extends B { constructor() { foo += super().a; this.c(); } }",
+        "class A extends B { constructor() { foo |= super().a; this.c(); } }",
+        "class A extends B { constructor() { foo &= super().a; this.c(); } }",
 
         // allows `this`/`super` in nested executable scopes, even if before `super()`.
         "class A extends B { constructor() { class B extends C { constructor() { super(); this.d = 0; } } super(); } }",
@@ -161,6 +165,21 @@ ruleTester.run("no-this-before-super", rule, {
         {
             code: "class A extends B { constructor() { try { super(); } catch (err) { } this.a; } }",
             errors: [{ messageId: "noBeforeSuper", data: { kind: "this" }, type: "ThisExpression" }]
+        },
+        {
+            code: "class A extends B { constructor() { foo &&= super().a; this.c(); } }",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "noBeforeSuper", data: { kind: "this" }, type: "ThisExpression" }]
+        },
+        {
+            code: "class A extends B { constructor() { foo ||= super().a; this.c(); } }",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "noBeforeSuper", data: { kind: "this" }, type: "ThisExpression" }]
+        },
+        {
+            code: "class A extends B { constructor() { foo ??= super().a; this.c(); } }",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{ messageId: "noBeforeSuper", data: { kind: "this" }, type: "ThisExpression" }]
         }
     ]
 });
index 05002117793de890fe04067cb825110f1a890634..3855b58fdfb295672db5372589b9cbb30ad89a3e 100644 (file)
@@ -30,7 +30,9 @@ ruleTester.run("no-throw-literal", rule, {
         "throw new foo();", // NewExpression
         "throw foo.bar;", // MemberExpression
         "throw foo[bar];", // MemberExpression
-        "throw foo = new Error();", // AssignmentExpression
+        "throw foo = new Error();", // AssignmentExpression with the `=` operator
+        { code: "throw foo.bar ||= 'literal'", parserOptions: { ecmaVersion: 2021 } }, // AssignmentExpression with a logical operator
+        { code: "throw foo[bar] ??= 'literal'", parserOptions: { ecmaVersion: 2021 } }, // AssignmentExpression with a logical operator
         "throw 1, 2, new Error();", // SequenceExpression
         "throw 'literal' && new Error();", // LogicalExpression (right)
         "throw new Error() || 'literal';", // LogicalExpression (left)
@@ -38,7 +40,9 @@ ruleTester.run("no-throw-literal", rule, {
         "throw foo ? 'literal' : new Error();", // ConditionalExpression (alternate)
         { code: "throw tag `${foo}`;", parserOptions: { ecmaVersion: 6 } }, // TaggedTemplateExpression
         { code: "function* foo() { var index = 0; throw yield index++; }", parserOptions: { ecmaVersion: 6 } }, // YieldExpression
-        { code: "async function foo() { throw await bar; }", parserOptions: { ecmaVersion: 8 } } // AwaitExpression
+        { code: "async function foo() { throw await bar; }", parserOptions: { ecmaVersion: 8 } }, // AwaitExpression
+        { code: "throw obj?.foo", parserOptions: { ecmaVersion: 2020 } }, // ChainExpression
+        { code: "throw obj?.foo()", parserOptions: { ecmaVersion: 2020 } } // ChainExpression
     ],
     invalid: [
         {
@@ -102,7 +106,29 @@ ruleTester.run("no-throw-literal", rule, {
 
         // AssignmentExpression
         {
-            code: "throw foo = 'error';",
+            code: "throw foo = 'error';", // RHS is a literal
+            errors: [{
+                messageId: "object",
+                type: "ThrowStatement"
+            }]
+        },
+        {
+            code: "throw foo += new Error();", // evaluates to a primitive value, or throws while evaluating
+            errors: [{
+                messageId: "object",
+                type: "ThrowStatement"
+            }]
+        },
+        {
+            code: "throw foo &= new Error();", // evaluates to a primitive value, or throws while evaluating
+            errors: [{
+                messageId: "object",
+                type: "ThrowStatement"
+            }]
+        },
+        {
+            code: "throw foo &&= 'literal'", // evaluates either to a falsy value of `foo` (which, then, cannot be an Error object), or to 'literal'
+            parserOptions: { ecmaVersion: 2021 },
             errors: [{
                 messageId: "object",
                 type: "ThrowStatement"
@@ -126,6 +152,13 @@ ruleTester.run("no-throw-literal", rule, {
                 type: "ThrowStatement"
             }]
         },
+        {
+            code: "throw foo && 'literal'", // evaluates either to a falsy value of `foo` (which, then, cannot be an Error object), or to 'literal'
+            errors: [{
+                messageId: "object",
+                type: "ThrowStatement"
+            }]
+        },
 
         // ConditionalExpression
         {
index 8c9c053a55a4ed1d66fc5c82c00a8702bd7f15bb..43697ec957d7cbe0ab1d678c40459d940679ff96 100644 (file)
@@ -59,6 +59,7 @@ ruleTester.run("no-undef", rule, {
         { code: "requestIdleCallback;", env: { browser: true } },
         { code: "customElements;", env: { browser: true } },
         { code: "PromiseRejectionEvent;", env: { browser: true } },
+        { code: "(foo, bar) => { foo ||= WeakRef; bar ??= FinalizationRegistry; }", env: { es2021: true } },
 
         // Notifications of readonly are removed: https://github.com/eslint/eslint/issues/4504
         "/*global b:false*/ function f() { b = 1; }",
index 4baef3e82dab9c8ee4dd45010b4b34d24e549a0a..89f20835de234d17bc6cb8c50389ea7a4e4558f0 100644 (file)
@@ -26,6 +26,18 @@ ruleTester.run("no-underscore-dangle", rule, {
         "console.log(__filename); console.log(__dirname);",
         "var _ = require('underscore');",
         "var a = b._;",
+        "function foo(_bar) {}",
+        "function foo(bar_) {}",
+        "(function _foo() {})",
+        { code: "function foo(_bar) {}", options: [{}] },
+        { code: "function foo( _bar = 0) {}", parserOptions: { ecmaVersion: 6 } },
+        { code: "const foo = { onClick(_bar) { } }", parserOptions: { ecmaVersion: 6 } },
+        { code: "const foo = { onClick(_bar = 0) { } }", parserOptions: { ecmaVersion: 6 } },
+        { code: "const foo = (_bar) => {}", parserOptions: { ecmaVersion: 6 } },
+        { code: "const foo = (_bar = 0) => {}", parserOptions: { ecmaVersion: 6 } },
+        { code: "function foo( ..._bar) {}", parserOptions: { ecmaVersion: 6 } },
+        { code: "const foo = (..._bar) => {}", parserOptions: { ecmaVersion: 6 } },
+        { code: "const foo = { onClick(..._bar) { } }", parserOptions: { ecmaVersion: 6 } },
         { code: "export default function() {}", parserOptions: { ecmaVersion: 6, sourceType: "module" } },
         { code: "var _foo = 1", options: [{ allow: ["_foo"] }] },
         { code: "var __proto__ = 1;", options: [{ allow: ["__proto__"] }] },
@@ -40,7 +52,24 @@ ruleTester.run("no-underscore-dangle", rule, {
         { code: "const o = { _onClick() { } }", options: [{ allow: ["_onClick"], enforceInMethodNames: true }], parserOptions: { ecmaVersion: 6 } },
         { code: "const o = { _foo: 'bar' }", parserOptions: { ecmaVersion: 6 } },
         { code: "const o = { foo_: 'bar' }", parserOptions: { ecmaVersion: 6 } },
-        { code: "this.constructor._bar", options: [{ allowAfterThisConstructor: true }] }
+        { code: "this.constructor._bar", options: [{ allowAfterThisConstructor: true }] },
+        { code: "const foo = { onClick(bar) { } }", parserOptions: { ecmaVersion: 6 } },
+        { code: "const foo = (bar) => {}", parserOptions: { ecmaVersion: 6 } },
+        { code: "function foo(_bar) {}", options: [{ allowFunctionParams: true }] },
+        { code: "function foo( _bar = 0) {}", options: [{ allowFunctionParams: true }], parserOptions: { ecmaVersion: 6 } },
+        { code: "const foo = { onClick(_bar) { } }", options: [{ allowFunctionParams: true }], parserOptions: { ecmaVersion: 6 } },
+        { code: "const foo = (_bar) => {}", options: [{ allowFunctionParams: true }], parserOptions: { ecmaVersion: 6 } },
+        { code: "function foo(bar) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } },
+        { code: "const foo = { onClick(bar) { } }", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } },
+        { code: "const foo = (bar) => {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } },
+        { code: "function foo(_bar) {}", options: [{ allowFunctionParams: false, allow: ["_bar"] }] },
+        { code: "const foo = { onClick(_bar) { } }", options: [{ allowFunctionParams: false, allow: ["_bar"] }], parserOptions: { ecmaVersion: 6 } },
+        { code: "const foo = (_bar) => {}", options: [{ allowFunctionParams: false, allow: ["_bar"] }], parserOptions: { ecmaVersion: 6 } },
+        { code: "function foo([_bar]) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } },
+        { code: "function foo([_bar] = []) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } },
+        { code: "function foo( { _bar }) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } },
+        { code: "function foo( { _bar = 0 } = {}) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } },
+        { code: "function foo(...[_bar]) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 2016 } }
     ],
     invalid: [
         { code: "var _foo = 1", errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_foo" }, type: "VariableDeclarator" }] },
@@ -57,6 +86,18 @@ ruleTester.run("no-underscore-dangle", rule, {
         { code: "const o = { _onClick() { } }", options: [{ enforceInMethodNames: true }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_onClick" }, type: "Property" }] },
         { code: "const o = { onClick_() { } }", options: [{ enforceInMethodNames: true }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "onClick_" }, type: "Property" }] },
         { code: "this.constructor._bar", errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "MemberExpression" }] },
-        { code: "foo.constructor._bar", options: [{ allowAfterThisConstructor: true }], errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "MemberExpression" }] }
+        { code: "function foo(_bar) {}", options: [{ allowFunctionParams: false }], errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "Identifier" }] },
+        { code: "(function foo(_bar) {})", options: [{ allowFunctionParams: false }], errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "Identifier" }] },
+        { code: "function foo(bar, _foo) {}", options: [{ allowFunctionParams: false }], errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_foo" }, type: "Identifier" }] },
+        { code: "const foo = { onClick(_bar) { } }", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "Identifier" }] },
+        { code: "const foo = (_bar) => {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "Identifier" }] },
+        { code: "function foo(_bar = 0) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "AssignmentPattern" }] },
+        { code: "const foo = { onClick(_bar = 0) { } }", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "AssignmentPattern" }] },
+        { code: "const foo = (_bar = 0) => {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "AssignmentPattern" }] },
+        { code: "function foo(..._bar) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "RestElement" }] },
+        { code: "const foo = { onClick(..._bar) { } }", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "RestElement" }] },
+        { code: "const foo = (..._bar) => {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "RestElement" }] }
+
+
     ]
 });
index 29d05a9215b1f9b3fdd8967b2345c27cd3e0704d..83c7bf67570a5481ee9f93db879be0f2f4f9a92b 100644 (file)
@@ -122,6 +122,24 @@ ruleTester.run("no-unexpected-multiline", rule, {
                 >\`multiline\`;
             `,
             parser: require.resolve("../../fixtures/parsers/typescript-parsers/tagged-template-with-generic/tagged-template-with-generic-3")
+        },
+
+        // Optional chaining
+        {
+            code: "var a = b\n  ?.(x || y).doSomething()",
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "var a = b\n  ?.[a, b, c].forEach(doSomething)",
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "var a = b?.\n  (x || y).doSomething()",
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "var a = b?.\n  [a, b, c].forEach(doSomething)",
+            parserOptions: { ecmaVersion: 2020 }
         }
     ],
     invalid: [
index 82479a94cb3a5e1755dd0dfdcee1984d544a4fb9..7ad11d2b2e51ac97c0dcde179c1dbdfcaab27aaa 100644 (file)
@@ -54,7 +54,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalExpression",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 19
+                column: 9,
+                endLine: 1,
+                endColumn: 31
             }]
         },
         {
@@ -64,7 +66,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalExpression",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 18
+                column: 9,
+                endLine: 1,
+                endColumn: 30
             }]
         },
         {
@@ -74,7 +78,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalExpression",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 13
+                column: 9,
+                endLine: 1,
+                endColumn: 25
             }]
         },
         {
@@ -84,7 +90,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalExpression",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 19
+                column: 9,
+                endLine: 1,
+                endColumn: 31
             }]
         },
         {
@@ -94,7 +102,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalExpression",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 18
+                column: 9,
+                endLine: 1,
+                endColumn: 30
             }]
         },
         {
@@ -104,7 +114,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalExpression",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 17
+                column: 9,
+                endLine: 1,
+                endColumn: 29
             }]
         },
         {
@@ -114,7 +126,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalExpression",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 18
+                column: 9,
+                endLine: 1,
+                endColumn: 30
             }]
         },
         {
@@ -124,7 +138,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalExpression",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 21
+                column: 9,
+                endLine: 1,
+                endColumn: 33
             }]
         },
         {
@@ -134,7 +150,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalExpression",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 28
+                column: 9,
+                endLine: 1,
+                endColumn: 40
             }]
         },
         {
@@ -144,7 +162,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalExpression",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 15
+                column: 9,
+                endLine: 1,
+                endColumn: 28
             }]
         },
         {
@@ -154,7 +174,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalExpression",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 17
+                column: 9,
+                endLine: 1,
+                endColumn: 30
             }]
         },
         {
@@ -164,7 +186,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalExpression",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 28
+                column: 9,
+                endLine: 1,
+                endColumn: 40
             }]
         },
         {
@@ -174,7 +198,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalExpression",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 16
+                column: 9,
+                endLine: 1,
+                endColumn: 28
             }]
         },
         {
@@ -193,7 +219,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalAssignment",
                 type: "ConditionalExpression",
                 line: 4,
-                column: 38
+                column: 30,
+                endLine: 4,
+                endColumn: 78
             }]
         },
         {
@@ -204,7 +232,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalAssignment",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 7
+                column: 1,
+                endLine: 1,
+                endColumn: 30
             }]
         },
         {
@@ -216,7 +246,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalAssignment",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 24
+                column: 18,
+                endLine: 1,
+                endColumn: 39
             }]
         },
         {
@@ -227,7 +259,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalAssignment",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 15
+                column: 9,
+                endLine: 1,
+                endColumn: 25
             }]
         },
         {
@@ -238,7 +272,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalAssignment",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 24
+                column: 9,
+                endLine: 1,
+                endColumn: 66
             }]
         },
         {
@@ -250,7 +286,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalAssignment",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 13
+                column: 9,
+                endLine: 1,
+                endColumn: 23
             }]
         },
         {
@@ -262,7 +300,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalAssignment",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 13
+                column: 9,
+                endLine: 1,
+                endColumn: 22
             }]
         },
         {
@@ -274,7 +314,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalAssignment",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 13
+                column: 9,
+                endLine: 1,
+                endColumn: 25
             }]
         },
         {
@@ -286,7 +328,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalAssignment",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 13
+                column: 9,
+                endLine: 1,
+                endColumn: 24
             }]
         },
         {
@@ -298,7 +342,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalAssignment",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 13
+                column: 9,
+                endLine: 1,
+                endColumn: 27
             }]
         },
         {
@@ -310,7 +356,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalAssignment",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 13
+                column: 9,
+                endLine: 1,
+                endColumn: 18
             }]
         },
         {
@@ -322,7 +370,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalAssignment",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 13
+                column: 9,
+                endLine: 1,
+                endColumn: 23
             }]
         },
         {
@@ -333,7 +383,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalAssignment",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 7
+                column: 3,
+                endLine: 1,
+                endColumn: 12
             }]
         },
         {
@@ -344,7 +396,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalAssignment",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 5
+                column: 1,
+                endLine: 1,
+                endColumn: 10
             }]
         },
         {
@@ -355,7 +409,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalAssignment",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 15
+                column: 9,
+                endLine: 1,
+                endColumn: 24
             }]
         },
         {
@@ -367,7 +423,9 @@ ruleTester.run("no-unneeded-ternary", rule, {
                 messageId: "unnecessaryConditionalAssignment",
                 type: "ConditionalExpression",
                 line: 1,
-                column: 15
+                column: 9,
+                endLine: 1,
+                endColumn: 27
             }]
         }
     ]
diff --git a/eslint/tests/lib/rules/no-unreachable-loop.js b/eslint/tests/lib/rules/no-unreachable-loop.js
new file mode 100644 (file)
index 0000000..10eaed4
--- /dev/null
@@ -0,0 +1,431 @@
+/**
+ * @fileoverview Tests for the no-unreachable-loop rule
+ * @author Milos Djermanovic
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const rule = require("../../../lib/rules/no-unreachable-loop");
+const { RuleTester } = require("../../../lib/rule-tester");
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } });
+
+const loopTemplates = {
+    WhileStatement: [
+        "while (a) <body>",
+        "while (a && b) <body>"
+    ],
+    DoWhileStatement: [
+        "do <body> while (a)",
+        "do <body> while (a && b)"
+    ],
+    ForStatement: [
+        "for (a; b; c) <body>",
+        "for (var i = 0; i < a.length; i++) <body>",
+        "for (; b; c) <body>",
+        "for (; b < foo; c++) <body>",
+        "for (a; ; c) <body>",
+        "for (a = 0; ; c++) <body>",
+        "for (a; b;) <body>",
+        "for (a = 0; b < foo; ) <body>",
+        "for (; ; c) <body>",
+        "for (; ; c++) <body>",
+        "for (; b;) <body>",
+        "for (; b < foo; ) <body>",
+        "for (a; ;) <body>",
+        "for (a = 0; ;) <body>",
+        "for (;;) <body>"
+    ],
+    ForInStatement: [
+        "for (a in b) <body>",
+        "for (a in f(b)) <body>",
+        "for (var a in b) <body>",
+        "for (let a in f(b)) <body>"
+    ],
+    ForOfStatement: [
+        "for (a of b) <body>",
+        "for (a of f(b)) <body>",
+        "for ({ a, b } of c) <body>",
+        "for (var a of f(b)) <body>",
+        "async function foo() { for await (const a of b) <body> }"
+    ]
+};
+
+const validLoopBodies = [
+    ";",
+    "{}",
+    "{ bar(); }",
+    "continue;",
+    "{ continue; }",
+    "{ if (foo) break; }",
+    "{ if (foo) { return; } bar(); }",
+    "{ if (foo) { bar(); } else { break; } }",
+    "{ if (foo) { continue; } return; }",
+    "{ switch (foo) { case 1: return; } }",
+    "{ switch (foo) { case 1: break; default: return; } }",
+    "{ switch (foo) { case 1: continue; default: return; } throw err; }",
+    "{ try { return bar(); } catch (e) {} }",
+
+    // unreachable break
+    "{ continue; break; }",
+
+    // functions in loops
+    "() => a;",
+    "{ () => a }",
+    "(() => a)();",
+    "{ (() => a)() }",
+
+    // loops in loops
+    "while (a);",
+    "do ; while (a)",
+    "for (a; b; c);",
+    "for (; b;);",
+    "for (; ; c) if (foo) break;",
+    "for (;;) if (foo) break;",
+    "while (true) if (foo) break;",
+    "while (foo) if (bar) return;",
+    "for (a in b);",
+    "for (a of b);"
+];
+
+const invalidLoopBodies = [
+    "break;",
+    "{ break; }",
+    "return;",
+    "{ return; }",
+    "throw err;",
+    "{ throw err; }",
+    "{ foo(); break; }",
+    "{ break; foo(); }",
+    "if (foo) break; else return;",
+    "{ if (foo) { return; } else { break; } bar(); }",
+    "{ if (foo) { return; } throw err; }",
+    "{ switch (foo) { default: throw err; } }",
+    "{ switch (foo) { case 1: throw err; default: return; } }",
+    "{ switch (foo) { case 1: something(); default: return; } }",
+    "{ try { return bar(); } catch (e) { break; } }",
+
+    // unreachable continue
+    "{ break; continue; }",
+
+    // functions in loops
+    "{ () => a; break; }",
+    "{ (() => a)(); break; }",
+
+    // loops in loops
+    "{ while (a); break; }",
+    "{ do ; while (a) break; }",
+    "{ for (a; b; c); break; }",
+    "{ for (; b;); break; }",
+    "{ for (; ; c) if (foo) break; break; }",
+    "{ for(;;) if (foo) break; break; }",
+    "{ for (a in b); break; }",
+    "{ for (a of b); break; }",
+
+    /**
+     * Additional cases where code path analysis detects unreachable code: after loops that don't have a test condition or have a
+     * constant truthy test condition, and at the same time don't have any exit statements in the body. These are special cases
+     * where this rule reports error not because the outer loop's body exits in all paths, but because it has an infinite loop
+     * inside, thus it (the outer loop) cannot have more than one iteration.
+     */
+    "for (;;);",
+    "{ for (var i = 0; ; i< 10) { foo(); } }",
+    "while (true);"
+];
+
+/**
+ * Creates source code from the given loop template and loop body.
+ * @param {string} template Loop template.
+ * @param {string} body Loop body.
+ * @returns {string} Source code.
+ */
+function getSourceCode(template, body) {
+    const loop = template.replace("<body>", body);
+
+    return body.includes("return") && !template.includes("function") ? `function someFunc() { ${loop} }` : loop;
+}
+
+/**
+ * Generates basic valid tests from `loopTemplates` and `validLoopBodies`
+ * @returns {IterableIterator<string>} The list of source code strings.
+ */
+function *getBasicValidTests() {
+    for (const templates of Object.values(loopTemplates)) {
+        for (const template of templates) {
+            yield* validLoopBodies.map(body => getSourceCode(template, body));
+        }
+    }
+}
+
+/**
+ * Generates basic invalid tests from `loopTemplates` and `invalidLoopBodies`
+ * @returns {IterableIterator<Object>} The list of objects for the invalid[] array.
+ */
+function *getBasicInvalidTests() {
+    for (const [type, templates] of Object.entries(loopTemplates)) {
+        for (const template of templates) {
+            yield* invalidLoopBodies.map(
+                body => ({
+                    code: getSourceCode(template, body),
+                    errors: [{ type, messageId: "invalid" }]
+                })
+            );
+        }
+    }
+}
+
+ruleTester.run("no-unreachable-loop", rule, {
+    valid: [
+
+        ...getBasicValidTests(),
+
+        // out of scope for the code path analysis and consequently out of scope for this rule
+        "while (false) { foo(); }",
+        "while (bar) { foo(); if (true) { break; } }",
+        "do foo(); while (false)",
+        "for (x = 1; x < 10; i++) { if (x > 0) { foo(); throw err; } }",
+        "for (x of []);",
+        "for (x of [1]);",
+
+        // doesn't report unreachable loop statements, regardless of whether they would be valid or not in a reachable position
+        "function foo() { return; while (a); }",
+        "function foo() { return; while (a) break; }",
+        "while(true); while(true);",
+        "while(true); while(true) break;",
+
+        // "ignore"
+        {
+            code: "while (a) break;",
+            options: [{ ignore: ["WhileStatement"] }]
+        },
+        {
+            code: "do break; while (a)",
+            options: [{ ignore: ["DoWhileStatement"] }]
+        },
+        {
+            code: "for (a; b; c) break;",
+            options: [{ ignore: ["ForStatement"] }]
+        },
+        {
+            code: "for (a in b) break;",
+            options: [{ ignore: ["ForInStatement"] }]
+        },
+        {
+            code: "for (a of b) break;",
+            options: [{ ignore: ["ForOfStatement"] }]
+        },
+        {
+            code: "for (var key in obj) { hasEnumerableProperties = true; break; } for (const a of b) break;",
+            options: [{ ignore: ["ForInStatement", "ForOfStatement"] }]
+        }
+    ],
+
+    invalid: [
+
+        ...getBasicInvalidTests(),
+
+        // invalid loop nested in a valid loop (valid in valid, and valid in invalid are covered by basic tests)
+        {
+            code: "while (foo) { for (a of b) { if (baz) { break; } else { throw err; } } }",
+            errors: [
+                {
+                    messageId: "invalid",
+                    type: "ForOfStatement"
+                }
+            ]
+        },
+        {
+            code: "lbl: for (var i = 0; i < 10; i++) { while (foo) break lbl; } /* outer is valid because inner can have 0 iterations */",
+            errors: [
+                {
+                    messageId: "invalid",
+                    type: "WhileStatement"
+                }
+            ]
+        },
+
+        // invalid loop nested in another invalid loop
+        {
+            code: "for (a in b) { while (foo) { if(baz) { break; } else { break; } } break; }",
+            errors: [
+                {
+                    messageId: "invalid",
+                    type: "ForInStatement"
+                },
+                {
+                    messageId: "invalid",
+                    type: "WhileStatement"
+                }
+            ]
+        },
+
+        // loop and nested loop both invalid because of the same exit statement
+        {
+            code: "function foo() { for (var i = 0; i < 10; i++) { do { return; } while(i) } }",
+            errors: [
+                {
+                    messageId: "invalid",
+                    type: "ForStatement"
+                },
+                {
+                    messageId: "invalid",
+                    type: "DoWhileStatement"
+                }
+            ]
+        },
+        {
+            code: "lbl: while(foo) { do { break lbl; } while(baz) }",
+            errors: [
+                {
+                    messageId: "invalid",
+                    type: "WhileStatement"
+                },
+                {
+                    messageId: "invalid",
+                    type: "DoWhileStatement"
+                }
+            ]
+        },
+
+        // inner loop has continue, but to an outer loop
+        {
+            code: "lbl: for (a in b) { while(foo) { continue lbl; } }",
+            errors: [
+                {
+                    messageId: "invalid",
+                    type: "WhileStatement"
+                }
+            ]
+        },
+
+        // edge cases - inner loop has only one exit path, but at the same time it exits the outer loop in the first iteration
+        {
+            code: "for (a of b) { for(;;) { if (foo) { throw err; } } }",
+            errors: [
+                {
+                    messageId: "invalid",
+                    type: "ForOfStatement"
+                }
+            ]
+        },
+        {
+            code: "function foo () { for (a in b) { while (true) { if (bar) { return; } } } }",
+            errors: [
+                {
+                    messageId: "invalid",
+                    type: "ForInStatement"
+                }
+            ]
+        },
+
+        // edge cases where parts of the loops belong to the same code path segment, tests for false negatives
+        {
+            code: "do for (var i = 1; i < 10; i++) break; while(foo)",
+            errors: [
+                {
+                    messageId: "invalid",
+                    type: "ForStatement"
+                }
+            ]
+        },
+        {
+            code: "do { for (var i = 1; i < 10; i++) continue; break; } while(foo)",
+            errors: [
+                {
+                    messageId: "invalid",
+                    type: "DoWhileStatement"
+                }
+            ]
+        },
+        {
+            code: "for (;;) { for (var i = 1; i < 10; i ++) break; if (foo) break; continue; }",
+            errors: [
+                {
+                    messageId: "invalid",
+                    type: "ForStatement",
+                    column: 12
+                }
+            ]
+        },
+
+        // "ignore"
+        {
+            code: "while (a) break; do break; while (b); for (;;) break; for (c in d) break; for (e of f) break;",
+            options: [{ ignore: [] }],
+            errors: [
+                {
+                    messageId: "invalid",
+                    type: "WhileStatement"
+                },
+                {
+                    messageId: "invalid",
+                    type: "DoWhileStatement"
+                },
+                {
+                    messageId: "invalid",
+                    type: "ForStatement"
+                },
+                {
+                    messageId: "invalid",
+                    type: "ForInStatement"
+                },
+                {
+                    messageId: "invalid",
+                    type: "ForOfStatement"
+                }
+            ]
+        },
+        {
+            code: "while (a) break;",
+            options: [{ ignore: ["DoWhileStatement"] }],
+            errors: [
+                {
+                    messageId: "invalid",
+                    type: "WhileStatement"
+                }
+            ]
+        },
+        {
+            code: "do break; while (a)",
+            options: [{ ignore: ["WhileStatement"] }],
+            errors: [
+                {
+                    messageId: "invalid",
+                    type: "DoWhileStatement"
+                }
+            ]
+        },
+        {
+            code: "for (a in b) break; for (c of d) break;",
+            options: [{ ignore: ["ForStatement"] }],
+            errors: [
+                {
+                    messageId: "invalid",
+                    type: "ForInStatement"
+                },
+                {
+                    messageId: "invalid",
+                    type: "ForOfStatement"
+                }
+            ]
+        },
+        {
+            code: "for (a in b) break; for (;;) break; for (c of d) break;",
+            options: [{ ignore: ["ForInStatement", "ForOfStatement"] }],
+            errors: [
+                {
+                    messageId: "invalid",
+                    type: "ForStatement"
+                }
+            ]
+        }
+    ]
+});
index 8ef2028c66207a6a90e8edb0264b8b30288f80a7..1674629c90cbf958061b4147dd4a79e8df429b2d 100644 (file)
@@ -74,6 +74,14 @@ ruleTester.run("no-unused-expressions", rule, {
         {
             code: "import(\"foo\")",
             parserOptions: { ecmaVersion: 11 }
+        },
+        {
+            code: "func?.(\"foo\")",
+            parserOptions: { ecmaVersion: 11 }
+        },
+        {
+            code: "obj?.foo(\"bar\")",
+            parserOptions: { ecmaVersion: 11 }
         }
     ],
     invalid: [
@@ -127,6 +135,23 @@ ruleTester.run("no-unused-expressions", rule, {
             options: [{ allowTaggedTemplates: false }],
             parserOptions: { ecmaVersion: 6 },
             errors: [{ messageId: "unusedExpression" }]
+        },
+
+        // Optional chaining
+        {
+            code: "obj?.foo",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unusedExpression", type: "ExpressionStatement" }]
+        },
+        {
+            code: "obj?.foo.bar",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unusedExpression", type: "ExpressionStatement" }]
+        },
+        {
+            code: "obj?.foo().bar",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unusedExpression", type: "ExpressionStatement" }]
         }
     ]
 });
index 8713ee92c4da6480da58f2bc5e2c3cbc69a24ead..528f2f1b1292cb2e854d94f2253a1fd2dab9b793 100644 (file)
@@ -44,7 +44,13 @@ ruleTester.run("no-useless-call", rule, {
         "foo.call();",
         "obj.foo.call();",
         "foo.apply();",
-        "obj.foo.apply();"
+        "obj.foo.apply();",
+
+        // Optional chaining
+        {
+            code: "obj?.foo.bar.call(obj.foo, 1, 2);",
+            parserOptions: { ecmaVersion: 2020 }
+        }
     ],
     invalid: [
 
@@ -170,6 +176,86 @@ ruleTester.run("no-useless-call", rule, {
                 data: { name: "apply" },
                 type: "CallExpression"
             }]
+        },
+
+        // Optional chaining
+        {
+            code: "foo.call?.(undefined, 1, 2);",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unnecessaryCall", data: { name: "call" } }]
+        },
+        {
+            code: "foo?.call(undefined, 1, 2);",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unnecessaryCall", data: { name: "call" } }]
+        },
+        {
+            code: "(foo?.call)(undefined, 1, 2);",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unnecessaryCall", data: { name: "call" } }]
+        },
+        {
+            code: "obj.foo.call?.(obj, 1, 2);",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{
+                messageId: "unnecessaryCall",
+                data: { name: "call" },
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "obj?.foo.call(obj, 1, 2);",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{
+                messageId: "unnecessaryCall",
+                data: { name: "call" },
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "(obj?.foo).call(obj, 1, 2);",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{
+                messageId: "unnecessaryCall",
+                data: { name: "call" },
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "(obj?.foo.call)(obj, 1, 2);",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{
+                messageId: "unnecessaryCall",
+                data: { name: "call" },
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "obj?.foo.bar.call(obj?.foo, 1, 2);",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{
+                messageId: "unnecessaryCall",
+                data: { name: "call" },
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "(obj?.foo).bar.call(obj?.foo, 1, 2);",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{
+                messageId: "unnecessaryCall",
+                data: { name: "call" },
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "obj.foo?.bar.call(obj.foo, 1, 2);",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{
+                messageId: "unnecessaryCall",
+                data: { name: "call" },
+                type: "CallExpression"
+            }]
         }
     ]
 });
index 0359b8706733d430df201a36fe3a10702a12e4e9..c2689defab101f2e93b07e4e649f574dce6da063 100644 (file)
@@ -43,165 +43,219 @@ ruleTester.run("no-warning-comments", rule, {
         {
             code: "// fixme",
             errors: [
-                { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } }
+                { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "fixme" } }
             ]
         },
         {
             code: "// any fixme",
             options: [{ location: "anywhere" }],
             errors: [
-                { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } }
+                { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "any fixme" } }
             ]
         },
         {
             code: "// any fixme",
             options: [{ terms: ["fixme"], location: "anywhere" }],
             errors: [
-                { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } }
+                { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "any fixme" } }
             ]
         },
         {
             code: "// any FIXME",
             options: [{ terms: ["fixme"], location: "anywhere" }],
             errors: [
-                { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } }
+                { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "any FIXME" } }
             ]
         },
         {
             code: "// any fIxMe",
             options: [{ terms: ["fixme"], location: "anywhere" }],
             errors: [
-                { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } }
+                { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "any fIxMe" } }
             ]
         },
         {
             code: "/* any fixme */",
             options: [{ terms: ["FIXME"], location: "anywhere" }],
             errors: [
-                { messageId: "unexpectedComment", data: { matchedTerm: "FIXME" } }
+                { messageId: "unexpectedComment", data: { matchedTerm: "FIXME", comment: "any fixme" } }
             ]
         },
         {
             code: "/* any FIXME */",
             options: [{ terms: ["FIXME"], location: "anywhere" }],
             errors: [
-                { messageId: "unexpectedComment", data: { matchedTerm: "FIXME" } }
+                { messageId: "unexpectedComment", data: { matchedTerm: "FIXME", comment: "any FIXME" } }
             ]
         },
         {
             code: "/* any fIxMe */",
             options: [{ terms: ["FIXME"], location: "anywhere" }],
             errors: [
-                { messageId: "unexpectedComment", data: { matchedTerm: "FIXME" } }
+                { messageId: "unexpectedComment", data: { matchedTerm: "FIXME", comment: "any fIxMe" } }
             ]
         },
         {
             code: "// any fixme or todo",
             options: [{ terms: ["fixme", "todo"], location: "anywhere" }],
             errors: [
-                { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } },
-                { messageId: "unexpectedComment", data: { matchedTerm: "todo" } }
+                { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "any fixme or todo" } },
+                { messageId: "unexpectedComment", data: { matchedTerm: "todo", comment: "any fixme or todo" } }
             ]
         },
         {
             code: "/* any fixme or todo */",
             options: [{ terms: ["fixme", "todo"], location: "anywhere" }],
             errors: [
-                { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } },
-                { messageId: "unexpectedComment", data: { matchedTerm: "todo" } }
+                { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "any fixme or todo" } },
+                { messageId: "unexpectedComment", data: { matchedTerm: "todo", comment: "any fixme or todo" } }
             ]
         },
         {
             code: "/* any fixme or todo */",
             options: [{ location: "anywhere" }],
             errors: [
-                { messageId: "unexpectedComment", data: { matchedTerm: "todo" } },
-                { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } }
+                { messageId: "unexpectedComment", data: { matchedTerm: "todo", comment: "any fixme or todo" } },
+                { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "any fixme or todo" } }
             ]
         },
         {
             code: "/* fixme and todo */",
             errors: [
-                { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } }
+                { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "fixme and todo" } }
             ]
         },
         {
             code: "/* fixme and todo */",
             options: [{ location: "anywhere" }],
             errors: [
-                { messageId: "unexpectedComment", data: { matchedTerm: "todo" } },
-                { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } }
+                { messageId: "unexpectedComment", data: { matchedTerm: "todo", comment: "fixme and todo" } },
+                { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "fixme and todo" } }
             ]
         },
         {
             code: "/* any fixme */",
             options: [{ location: "anywhere" }],
             errors: [
-                { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } }
+                { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "any fixme" } }
             ]
         },
         {
             code: "/* fixme! */",
             options: [{ terms: ["fixme"] }],
             errors: [
-                { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } }
+                { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "fixme!" } }
             ]
         },
         {
             code: "// regex [litera|$]",
             options: [{ terms: ["[litera|$]"], location: "anywhere" }],
             errors: [
-                { messageId: "unexpectedComment", data: { matchedTerm: "[litera|$]" } }
+                { messageId: "unexpectedComment", data: { matchedTerm: "[litera|$]", comment: "regex [litera|$]" } }
             ]
         },
         {
             code: "/* eslint one-var: 2 */",
             options: [{ terms: ["eslint"] }],
             errors: [
-                { messageId: "unexpectedComment", data: { matchedTerm: "eslint" } }
+                { messageId: "unexpectedComment", data: { matchedTerm: "eslint", comment: "eslint one-var: 2" } }
             ]
         },
         {
             code: "/* eslint one-var: 2 */",
             options: [{ terms: ["one"], location: "anywhere" }],
             errors: [
-                { messageId: "unexpectedComment", data: { matchedTerm: "one" } }
+                { messageId: "unexpectedComment", data: { matchedTerm: "one", comment: "eslint one-var: 2" } }
             ]
         },
         {
             code: "/* any block comment with TODO, FIXME or XXX */",
             options: [{ location: "anywhere" }],
             errors: [
-                { messageId: "unexpectedComment", data: { matchedTerm: "todo" } },
-                { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } },
-                { messageId: "unexpectedComment", data: { matchedTerm: "xxx" } }
+                { messageId: "unexpectedComment", data: { matchedTerm: "todo", comment: "any block comment with TODO, FIXME or..." } },
+                { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "any block comment with TODO, FIXME or..." } },
+                { messageId: "unexpectedComment", data: { matchedTerm: "xxx", comment: "any block comment with TODO, FIXME or..." } }
             ]
         },
         {
             code: "/* any block comment with (TODO, FIXME's or XXX!) */",
             options: [{ location: "anywhere" }],
             errors: [
-                { messageId: "unexpectedComment", data: { matchedTerm: "todo" } },
-                { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } },
-                { messageId: "unexpectedComment", data: { matchedTerm: "xxx" } }
+                { messageId: "unexpectedComment", data: { matchedTerm: "todo", comment: "any block comment with (TODO, FIXME's or..." } },
+                { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "any block comment with (TODO, FIXME's or..." } },
+                { messageId: "unexpectedComment", data: { matchedTerm: "xxx", comment: "any block comment with (TODO, FIXME's or..." } }
             ]
         },
         {
             code: "/** \n *any block comment \n*with (TODO, FIXME's or XXX!) **/",
             options: [{ location: "anywhere" }],
             errors: [
-                { messageId: "unexpectedComment", data: { matchedTerm: "todo" } },
-                { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } },
-                { messageId: "unexpectedComment", data: { matchedTerm: "xxx" } }
+                { messageId: "unexpectedComment", data: { matchedTerm: "todo", comment: "* *any block comment *with (TODO,..." } },
+                { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "* *any block comment *with (TODO,..." } },
+                { messageId: "unexpectedComment", data: { matchedTerm: "xxx", comment: "* *any block comment *with (TODO,..." } }
             ]
         },
         {
             code: "// any comment with TODO, FIXME or XXX",
             options: [{ location: "anywhere" }],
             errors: [
-                { messageId: "unexpectedComment", data: { matchedTerm: "todo" } },
-                { messageId: "unexpectedComment", data: { matchedTerm: "fixme" } },
-                { messageId: "unexpectedComment", data: { matchedTerm: "xxx" } }
+                { messageId: "unexpectedComment", data: { matchedTerm: "todo", comment: "any comment with TODO, FIXME or XXX" } },
+                { messageId: "unexpectedComment", data: { matchedTerm: "fixme", comment: "any comment with TODO, FIXME or XXX" } },
+                { messageId: "unexpectedComment", data: { matchedTerm: "xxx", comment: "any comment with TODO, FIXME or XXX" } }
+            ]
+        },
+        {
+            code: "// TODO: something small",
+            options: [{ location: "anywhere" }],
+            errors: [
+                { messageId: "unexpectedComment", data: { matchedTerm: "todo", comment: "TODO: something small" } }
+            ]
+        },
+        {
+            code: "// TODO: something really longer than 40 characters",
+            options: [{ location: "anywhere" }],
+            errors: [
+                { messageId: "unexpectedComment", data: { matchedTerm: "todo", comment: "TODO: something really longer than 40..." } }
+            ]
+        },
+        {
+            code:
+                "/* TODO: something \n really longer than 40 characters \n and also a new line */",
+            options: [{ location: "anywhere" }],
+            errors: [
+                {
+                    messageId: "unexpectedComment",
+                    data: {
+                        matchedTerm: "todo",
+                        comment: "TODO: something really longer than 40..."
+                    }
+                }
+            ]
+        },
+        {
+            code: "// TODO: small",
+            options: [{ location: "anywhere" }],
+            errors: [
+                {
+                    messageId: "unexpectedComment",
+                    data: {
+                        matchedTerm: "todo",
+                        comment: "TODO: small"
+                    }
+                }
+            ]
+        },
+        {
+            code: "// https://github.com/eslint/eslint/pull/13522#discussion_r470293411 TODO",
+            options: [{ location: "anywhere" }],
+            errors: [
+                {
+                    messageId: "unexpectedComment",
+                    data: {
+                        matchedTerm: "todo",
+                        comment: "..."
+                    }
+                }
             ]
         }
     ]
index d10b30e20e84c4b9b3fa433a148b016d7cde09d4..485588dc9d528f771f70893601949e998a212d87 100644 (file)
@@ -99,7 +99,45 @@ ruleTester.run("no-whitespace-before-property", rule, {
         "foo[bar.baz('qux')]",
         "foo[(bar.baz() + 0) + qux]",
         "foo['bar ' + 1 + ' baz']",
-        "5['toExponential']()"
+        "5['toExponential']()",
+
+        // Optional chaining
+        {
+            code: "obj?.prop",
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "( obj )?.prop",
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "obj\n  ?.prop",
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "obj?.\n  prop",
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "obj?.[key]",
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "( obj )?.[ key ]",
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "obj\n  ?.[key]",
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "obj?.\n  [key]",
+            parserOptions: { ecmaVersion: 2020 }
+        },
+        {
+            code: "obj\n  ?.\n  [key]",
+            parserOptions: { ecmaVersion: 2020 }
+        }
     ],
 
     invalid: [
@@ -804,6 +842,40 @@ ruleTester.run("no-whitespace-before-property", rule, {
                 data: { propName: "toExponential" }
             }]
         },
+        {
+            code: "08      .toExponential()",
+            output: null, // Not fixed
+            errors: [{
+                messageId: "unexpectedWhitespace",
+                data: { propName: "toExponential" }
+            }]
+        },
+        {
+            code: "0192    .toExponential()",
+            output: null, // Not fixed
+            errors: [{
+                messageId: "unexpectedWhitespace",
+                data: { propName: "toExponential" }
+            }]
+        },
+        {
+            code: "5_000       .toExponential()",
+            output: null, // Not fixed,
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{
+                messageId: "unexpectedWhitespace",
+                data: { propName: "toExponential" }
+            }]
+        },
+        {
+            code: "5_000_00       .toExponential()",
+            output: null, // Not fixed,
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{
+                messageId: "unexpectedWhitespace",
+                data: { propName: "toExponential" }
+            }]
+        },
         {
             code: "5. .toExponential()",
             output: "5..toExponential()",
@@ -820,6 +892,15 @@ ruleTester.run("no-whitespace-before-property", rule, {
                 data: { propName: "toExponential" }
             }]
         },
+        {
+            code: "5.0_0 .toExponential()",
+            output: "5.0_0.toExponential()",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{
+                messageId: "unexpectedWhitespace",
+                data: { propName: "toExponential" }
+            }]
+        },
         {
             code: "0x5 .toExponential()",
             output: "0x5.toExponential()",
@@ -828,6 +909,15 @@ ruleTester.run("no-whitespace-before-property", rule, {
                 data: { propName: "toExponential" }
             }]
         },
+        {
+            code: "0x56_78 .toExponential()",
+            output: "0x56_78.toExponential()",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{
+                messageId: "unexpectedWhitespace",
+                data: { propName: "toExponential" }
+            }]
+        },
         {
             code: "5e0 .toExponential()",
             output: "5e0.toExponential()",
@@ -859,6 +949,56 @@ ruleTester.run("no-whitespace-before-property", rule, {
                 messageId: "unexpectedWhitespace",
                 data: { propName: "toExponential" }
             }]
+        },
+
+        // Optional chaining
+        {
+            code: "obj?. prop",
+            output: "obj?.prop",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpectedWhitespace", data: { propName: "prop" } }]
+        },
+        {
+            code: "obj ?.prop",
+            output: "obj?.prop",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpectedWhitespace", data: { propName: "prop" } }]
+        },
+        {
+            code: "obj?. [key]",
+            output: "obj?.[key]",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpectedWhitespace", data: { propName: "key" } }]
+        },
+        {
+            code: "obj ?.[key]",
+            output: "obj?.[key]",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpectedWhitespace", data: { propName: "key" } }]
+        },
+        {
+            code: "5 ?. prop",
+            output: "5?.prop",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpectedWhitespace", data: { propName: "prop" } }]
+        },
+        {
+            code: "5 ?. [key]",
+            output: "5?.[key]",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpectedWhitespace", data: { propName: "key" } }]
+        },
+        {
+            code: "obj/* comment */?. prop",
+            output: null,
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpectedWhitespace", data: { propName: "prop" } }]
+        },
+        {
+            code: "obj ?./* comment */prop",
+            output: null,
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "unexpectedWhitespace", data: { propName: "prop" } }]
         }
     ]
 });
index ad3b45218b3bfde8b817245ea539e048243f02d7..86e9560992016c65d1ec6e87ebb558dbada5b17e 100644 (file)
@@ -607,8 +607,20 @@ ruleTester.run("object-curly-newline", rule, {
             ].join("\n"),
             options: ["always"],
             errors: [
-                { line: 1, column: 9, messageId: "expectedLinebreakAfterOpeningBrace" },
-                { line: 1, column: 14, messageId: "expectedLinebreakBeforeClosingBrace" }
+                {
+                    line: 1,
+                    column: 9,
+                    endLine: 1,
+                    endColumn: 10,
+                    messageId: "expectedLinebreakAfterOpeningBrace"
+                },
+                {
+                    line: 1,
+                    column: 14,
+                    endLine: 1,
+                    endColumn: 15,
+                    messageId: "expectedLinebreakBeforeClosingBrace"
+                }
             ]
         },
         {
@@ -717,8 +729,20 @@ ruleTester.run("object-curly-newline", rule, {
             ].join("\n"),
             options: ["never"],
             errors: [
-                { line: 1, column: 9, messageId: "unexpectedLinebreakAfterOpeningBrace" },
-                { line: 3, column: 1, messageId: "unexpectedLinebreakBeforeClosingBrace" }
+                {
+                    line: 1,
+                    column: 9,
+                    endLine: 1,
+                    endColumn: 10,
+                    messageId: "unexpectedLinebreakAfterOpeningBrace"
+                },
+                {
+                    line: 3,
+                    column: 1,
+                    endLine: 3,
+                    endColumn: 2,
+                    messageId: "unexpectedLinebreakBeforeClosingBrace"
+                }
             ]
         },
         {
index 132d7a3e8681f1168b386fa325fe0c5efcaec710..e6499b9d7d7ccca15e04b8f6b1e2078d0b2711e8 100644 (file)
@@ -78,13 +78,31 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 1,
-                    column: 25
+                    column: 25,
+                    endLine: 1,
+                    endColumn: 27
                 },
                 {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 1,
-                    column: 37
+                    column: 37,
+                    endLine: 1,
+                    endColumn: 39
+                }
+            ]
+        },
+        {
+            code: "var obj = { k1: 'val1', k2: \n'val2', \nk3: 'val3' };",
+            output: "var obj = { k1: 'val1',\nk2: \n'val2', \nk3: 'val3' };",
+            errors: [
+                {
+                    messageId: "propertiesOnNewline",
+                    type: "ObjectExpression",
+                    line: 1,
+                    column: 25,
+                    endLine: 1,
+                    endColumn: 27
                 }
             ]
         },
@@ -96,7 +114,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 2,
-                    column: 13
+                    column: 13,
+                    endLine: 2,
+                    endColumn: 15
                 }
             ]
         },
@@ -108,13 +128,17 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 2,
-                    column: 13
+                    column: 13,
+                    endLine: 2,
+                    endColumn: 15
                 },
                 {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 3,
-                    column: 13
+                    column: 13,
+                    endLine: 3,
+                    endColumn: 15
                 }
             ]
         },
@@ -126,7 +150,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 1,
-                    column: 32
+                    column: 32,
+                    endLine: 1,
+                    endColumn: 34
                 }
             ]
         },
@@ -138,7 +164,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 3,
-                    column: 4
+                    column: 4,
+                    endLine: 3,
+                    endColumn: 6
                 }
             ]
         },
@@ -150,19 +178,25 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 2,
-                    column: 13
+                    column: 13,
+                    endLine: 2,
+                    endColumn: 15
                 },
                 {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 2,
-                    column: 29
+                    column: 29,
+                    endLine: 2,
+                    endColumn: 31
                 },
                 {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 2,
-                    column: 41
+                    column: 41,
+                    endLine: 2,
+                    endColumn: 43
                 }
             ]
         },
@@ -174,7 +208,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 3,
-                    column: 17
+                    column: 17,
+                    endLine: 3,
+                    endColumn: 19
                 }
             ]
         },
@@ -186,7 +222,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 4,
-                    column: 4
+                    column: 4,
+                    endLine: 4,
+                    endColumn: 6
                 }
             ]
         },
@@ -199,7 +237,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 1,
-                    column: 25
+                    column: 25,
+                    endLine: 1,
+                    endColumn: 26
                 }
             ]
         },
@@ -212,7 +252,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 1,
-                    column: 25
+                    column: 25,
+                    endLine: 1,
+                    endColumn: 28
                 }
             ]
         },
@@ -225,7 +267,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 2,
-                    column: 13
+                    column: 13,
+                    endLine: 2,
+                    endColumn: 16
                 }
             ]
         },
@@ -237,7 +281,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 1,
-                    column: 19
+                    column: 19,
+                    endLine: 1,
+                    endColumn: 21
                 }
             ]
         },
@@ -249,7 +295,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 2,
-                    column: 13
+                    column: 13,
+                    endLine: 2,
+                    endColumn: 15
                 }
             ]
         },
@@ -262,7 +310,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 1,
-                    column: 10
+                    column: 10,
+                    endLine: 1,
+                    endColumn: 11
                 }
             ]
         },
@@ -275,7 +325,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 2,
-                    column: 4
+                    column: 4,
+                    endLine: 2,
+                    endColumn: 5
                 }
             ]
         },
@@ -288,7 +340,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 2,
-                    column: 11
+                    column: 11,
+                    endLine: 2,
+                    endColumn: 14
                 }
             ]
         },
@@ -301,7 +355,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 2,
-                    column: 15
+                    column: 15,
+                    endLine: 2,
+                    endColumn: 18
                 }
             ]
         },
@@ -314,7 +370,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 1,
-                    column: 19
+                    column: 19,
+                    endLine: 1,
+                    endColumn: 20
                 }
             ]
         },
@@ -327,7 +385,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 1,
-                    column: 19
+                    column: 19,
+                    endLine: 1,
+                    endColumn: 22
                 }
             ]
         },
@@ -340,7 +400,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 2,
-                    column: 13
+                    column: 13,
+                    endLine: 2,
+                    endColumn: 16
                 }
             ]
         },
@@ -352,7 +414,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 5,
-                    column: 4
+                    column: 4,
+                    endLine: 5,
+                    endColumn: 5
                 }
             ]
         },
@@ -364,7 +428,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 1,
-                    column: 26
+                    column: 26,
+                    endLine: 1,
+                    endColumn: 29
                 }
             ]
         },
@@ -376,7 +442,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewline",
                     type: "ObjectExpression",
                     line: 1,
-                    column: 26
+                    column: 26,
+                    endLine: 1,
+                    endColumn: 29
                 }
             ]
         },
@@ -391,7 +459,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewlineAll",
                     type: "ObjectExpression",
                     line: 3,
-                    column: 13
+                    column: 13,
+                    endLine: 3,
+                    endColumn: 15
                 }
             ]
         },
@@ -404,13 +474,17 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewlineAll",
                     type: "ObjectExpression",
                     line: 3,
-                    column: 9
+                    column: 9,
+                    endLine: 3,
+                    endColumn: 11
                 },
                 {
                     messageId: "propertiesOnNewlineAll",
                     type: "ObjectExpression",
                     line: 3,
-                    column: 21
+                    column: 21,
+                    endLine: 3,
+                    endColumn: 23
                 }
             ]
         },
@@ -423,7 +497,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewlineAll",
                     type: "ObjectExpression",
                     line: 4,
-                    column: 4
+                    column: 4,
+                    endLine: 4,
+                    endColumn: 6
                 }
             ]
         },
@@ -436,7 +512,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewlineAll",
                     type: "ObjectExpression",
                     line: 3,
-                    column: 4
+                    column: 4,
+                    endLine: 3,
+                    endColumn: 6
                 }
             ]
         },
@@ -449,13 +527,17 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewlineAll",
                     type: "ObjectExpression",
                     line: 2,
-                    column: 13
+                    column: 13,
+                    endLine: 2,
+                    endColumn: 15
                 },
                 {
                     messageId: "propertiesOnNewlineAll",
                     type: "ObjectExpression",
                     line: 4,
-                    column: 4
+                    column: 4,
+                    endLine: 4,
+                    endColumn: 6
                 }
             ]
         },
@@ -468,7 +550,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewlineAll",
                     type: "ObjectExpression",
                     line: 4,
-                    column: 4
+                    column: 4,
+                    endLine: 4,
+                    endColumn: 6
                 }
             ]
         },
@@ -482,7 +566,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewlineAll",
                     type: "ObjectExpression",
                     line: 2,
-                    column: 14
+                    column: 14,
+                    endLine: 2,
+                    endColumn: 16
                 }
             ]
         },
@@ -496,7 +582,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewlineAll",
                     type: "ObjectExpression",
                     line: 3,
-                    column: 13
+                    column: 13,
+                    endLine: 3,
+                    endColumn: 16
                 }
             ]
         },
@@ -510,7 +598,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewlineAll",
                     type: "ObjectExpression",
                     line: 3,
-                    column: 13
+                    column: 13,
+                    endLine: 3,
+                    endColumn: 15
                 }
             ]
         },
@@ -524,7 +614,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewlineAll",
                     type: "ObjectExpression",
                     line: 2,
-                    column: 14
+                    column: 14,
+                    endLine: 2,
+                    endColumn: 16
                 }
             ]
         },
@@ -538,7 +630,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewlineAll",
                     type: "ObjectExpression",
                     line: 3,
-                    column: 13
+                    column: 13,
+                    endLine: 3,
+                    endColumn: 16
                 }
             ]
         },
@@ -552,7 +646,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewlineAll",
                     type: "ObjectExpression",
                     line: 3,
-                    column: 13
+                    column: 13,
+                    endLine: 3,
+                    endColumn: 15
                 }
             ]
         },
@@ -567,7 +663,9 @@ ruleTester.run("object-property-newline", rule, {
                     messageId: "propertiesOnNewlineAll",
                     type: "ObjectExpression",
                     line: 3,
-                    column: 13
+                    column: 13,
+                    endLine: 3,
+                    endColumn: 15
                 }
             ]
         }
index f690f7eb7f949e00ae5f02b264f8f9b8b73bf53a..1d07b0cd8ed32886c8c0bf4f239466280108b846 100644 (file)
@@ -16,7 +16,7 @@ const rule = require("../../../lib/rules/operator-assignment"),
 // Tests
 //------------------------------------------------------------------------------
 
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 7 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2021 } });
 
 const EXPECTED_OPERATOR_ASSIGNMENT = [{ messageId: "replaced", type: "AssignmentExpression" }];
 const UNEXPECTED_OPERATOR_ASSIGNMENT = [{ messageId: "unexpected", type: "AssignmentExpression" }];
@@ -84,6 +84,32 @@ ruleTester.run("operator-assignment", rule, {
         {
             code: "this.x = foo.this.x + y",
             options: ["always"]
+        },
+
+        // does not check logical operators
+        {
+            code: "x = x && y",
+            options: ["always"]
+        },
+        {
+            code: "x = x || y",
+            options: ["always"]
+        },
+        {
+            code: "x = x ?? y",
+            options: ["always"]
+        },
+        {
+            code: "x &&= y",
+            options: ["never"]
+        },
+        {
+            code: "x ||= y",
+            options: ["never"]
+        },
+        {
+            code: "x ??= y",
+            options: ["never"]
         }
     ],
 
@@ -398,6 +424,20 @@ ruleTester.run("operator-assignment", rule, {
         output: "foo= foo+(+bar===baz)", // tokens cannot be adjacent, but the right side will be parenthesised
         options: ["never"],
         errors: UNEXPECTED_OPERATOR_ASSIGNMENT
-    }]
+    },
+
+    // Optional chaining
+    {
+        code: "(obj?.a).b = (obj?.a).b + y",
+        output: null,
+        errors: EXPECTED_OPERATOR_ASSIGNMENT
+    },
+    {
+        code: "obj.a = obj?.a + b",
+        output: null,
+        errors: EXPECTED_OPERATOR_ASSIGNMENT
+    }
+
+    ]
 
 });
index 18a37e3fc80aca6211fd111f8912f80427be9eaa..1eeb9f5790e31fb9f15d34b94ff9b9b16a9eccec 100644 (file)
@@ -57,7 +57,48 @@ ruleTester.run("operator-linebreak", rule, {
         { code: "1 + 1\n", options: ["none"] },
         { code: "answer = everything ? 42 : foo;", options: ["none"] },
         { code: "answer = everything \n?\n 42 : foo;", options: [null, { overrides: { "?": "ignore" } }] },
-        { code: "answer = everything ? 42 \n:\n foo;", options: [null, { overrides: { ":": "ignore" } }] }
+        { code: "answer = everything ? 42 \n:\n foo;", options: [null, { overrides: { ":": "ignore" } }] },
+
+        {
+            code: "a \n &&= b",
+            options: ["after", { overrides: { "&&=": "ignore" } }],
+            parserOptions: { ecmaVersion: 2021 }
+        },
+        {
+            code: "a ??= \n b",
+            options: ["before", { overrides: { "??=": "ignore" } }],
+            parserOptions: { ecmaVersion: 2021 }
+        },
+        {
+            code: "a ||= \n b",
+            options: ["after", { overrides: { "=": "before" } }],
+            parserOptions: { ecmaVersion: 2021 }
+        },
+        {
+            code: "a \n &&= b",
+            options: ["before", { overrides: { "&=": "after" } }],
+            parserOptions: { ecmaVersion: 2021 }
+        },
+        {
+            code: "a \n ||= b",
+            options: ["before", { overrides: { "|=": "after" } }],
+            parserOptions: { ecmaVersion: 2021 }
+        },
+        {
+            code: "a &&= \n b",
+            options: ["after", { overrides: { "&&": "before" } }],
+            parserOptions: { ecmaVersion: 2021 }
+        },
+        {
+            code: "a ||= \n b",
+            options: ["after", { overrides: { "||": "before" } }],
+            parserOptions: { ecmaVersion: 2021 }
+        },
+        {
+            code: "a ??= \n b",
+            options: ["after", { overrides: { "??": "before" } }],
+            parserOptions: { ecmaVersion: 2021 }
+        }
     ],
 
     invalid: [
@@ -638,6 +679,97 @@ ruleTester.run("operator-linebreak", rule, {
                 messageId: "operatorAtBeginning",
                 data: { operator: "??" }
             }]
+        },
+
+        {
+            code: "a \n  &&= b",
+            output: "a &&= \n  b",
+            options: ["after"],
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{
+                messageId: "operatorAtEnd",
+                data: { operator: "&&=" },
+                type: "AssignmentExpression",
+                line: 2,
+                column: 3,
+                endLine: 2,
+                endColumn: 6
+            }]
+        },
+        {
+            code: "a ||=\n b",
+            output: "a\n ||= b",
+            options: ["before"],
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{
+                messageId: "operatorAtBeginning",
+                data: { operator: "||=" },
+                type: "AssignmentExpression",
+                line: 1,
+                column: 3,
+                endLine: 1,
+                endColumn: 6
+            }]
+        },
+        {
+            code: "a  ??=\n b",
+            output: "a  ??= b",
+            options: ["none"],
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{
+                messageId: "noLinebreak",
+                data: { operator: "??=" },
+                type: "AssignmentExpression",
+                line: 1,
+                column: 4,
+                endLine: 1,
+                endColumn: 7
+            }]
+        },
+        {
+            code: "a \n  &&= b",
+            output: "a   &&= b",
+            options: ["before", { overrides: { "&&=": "none" } }],
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{
+                messageId: "noLinebreak",
+                data: { operator: "&&=" },
+                type: "AssignmentExpression",
+                line: 2,
+                column: 3,
+                endLine: 2,
+                endColumn: 6
+            }]
+        },
+        {
+            code: "a ||=\nb",
+            output: "a\n||= b",
+            options: ["after", { overrides: { "||=": "before" } }],
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{
+                messageId: "operatorAtBeginning",
+                data: { operator: "||=" },
+                type: "AssignmentExpression",
+                line: 1,
+                column: 3,
+                endLine: 1,
+                endColumn: 6
+            }]
+        },
+        {
+            code: "a\n??=b",
+            output: "a??=\nb",
+            options: ["none", { overrides: { "??=": "after" } }],
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{
+                messageId: "operatorAtEnd",
+                data: { operator: "??=" },
+                type: "AssignmentExpression",
+                line: 2,
+                column: 1,
+                endLine: 2,
+                endColumn: 4
+            }]
         }
     ]
 });
index e553610e353c910a9ec09b2065441feff4c6ee1d..931cdd24ccb4152823176dec22173da9f3ee8111 100644 (file)
@@ -3632,6 +3632,22 @@ ruleTester.run("padding-line-between-statements", rule, {
             errors: [{ messageId: "expectedBlankLine" }]
         },
 
+        // Optional chaining
+        {
+            code: "(function(){\n})?.()\nvar a = 2;",
+            output: "(function(){\n})?.()\n\nvar a = 2;",
+            options: [{ blankLine: "always", prev: "iife", next: "*" }],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "expectedBlankLine" }]
+        },
+        {
+            code: "void (function(){\n})?.()\nvar a = 2;",
+            output: "void (function(){\n})?.()\n\nvar a = 2;",
+            options: [{ blankLine: "always", prev: "iife", next: "*" }],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "expectedBlankLine" }]
+        },
+
         //----------------------------------------------------------------------
         // import
         //----------------------------------------------------------------------
index cb380de251d4fc3ea6050c3d90c58cc409b4a88a..cb46c4a5021a13cb47d91a8b78a4cc5f272392d3 100644 (file)
@@ -21,7 +21,7 @@ const errors = [{
     type: "FunctionExpression"
 }];
 
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } });
 
 ruleTester.run("prefer-arrow-callback", rule, {
     valid: [
@@ -40,7 +40,9 @@ ruleTester.run("prefer-arrow-callback", rule, {
         "foo(function bar() { arguments; }.bind(this));",
         "foo(function bar() { new.target; });",
         "foo(function bar() { new.target; }.bind(this));",
-        "foo(function bar() { this; }.bind(this, somethingElse));"
+        "foo(function bar() { this; }.bind(this, somethingElse));",
+        "foo((function() {}).bind.bar)",
+        "foo((function() { this.bar(); }).bind(obj).bind(this))"
     ],
     invalid: [
         {
@@ -165,13 +167,43 @@ ruleTester.run("prefer-arrow-callback", rule, {
         {
             code: "qux(async function (foo = 1, bar = 2, baz = 3) { return baz; })",
             output: "qux(async (foo = 1, bar = 2, baz = 3) => { return baz; })",
-            parserOptions: { ecmaVersion: 8 },
             errors
         },
         {
             code: "qux(async function (foo = 1, bar = 2, baz = 3) { return this; }.bind(this))",
             output: "qux(async (foo = 1, bar = 2, baz = 3) => { return this; })",
-            parserOptions: { ecmaVersion: 8 },
+            errors
+        },
+        {
+            code: "foo((bar || function() {}).bind(this))",
+            output: null,
+            errors
+        },
+        {
+            code: "foo(function() {}.bind(this).bind(obj))",
+            output: "foo((() => {}).bind(obj))",
+            errors
+        },
+
+        // Optional chaining
+        {
+            code: "foo?.(function() {});",
+            output: "foo?.(() => {});",
+            errors
+        },
+        {
+            code: "foo?.(function() { return this; }.bind(this));",
+            output: "foo?.(() => { return this; });",
+            errors
+        },
+        {
+            code: "foo(function() { return this; }?.bind(this));",
+            output: "foo(() => { return this; });",
+            errors
+        },
+        {
+            code: "foo((function() { return this; }?.bind)(this));",
+            output: null,
             errors
         }
     ]
index 6f1b6e3a1be6a56bc4a43a0645a611e3d567726a..8f111a1d8c2f0f7d4fe1e2292e7c3ddaf86b4118 100644 (file)
@@ -16,7 +16,7 @@ const rule = require("../../../lib/rules/prefer-destructuring"),
 // Tests
 //------------------------------------------------------------------------------
 
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } });
 
 ruleTester.run("prefer-destructuring", rule, {
     valid: [
@@ -98,7 +98,19 @@ ruleTester.run("prefer-destructuring", rule, {
         },
         "[foo] = array;",
         "foo += array[0]",
+        {
+            code: "foo &&= array[0]",
+            parserOptions: { ecmaVersion: 2021 }
+        },
         "foo += bar.foo",
+        {
+            code: "foo ||= bar.foo",
+            parserOptions: { ecmaVersion: 2021 }
+        },
+        {
+            code: "foo ??= bar['foo']",
+            parserOptions: { ecmaVersion: 2021 }
+        },
         {
             code: "foo = object.foo;",
             options: [{ AssignmentExpression: { object: false } }, { enforceForRenamedProperties: true }]
@@ -141,7 +153,11 @@ ruleTester.run("prefer-destructuring", rule, {
         {
             code: "var {bar} = object.foo;",
             options: [{ object: true }]
-        }
+        },
+
+        // Optional chaining
+        "var foo = array?.[0];", // because the fixed code can throw TypeError.
+        "var foo = object?.foo;"
     ],
 
     invalid: [
@@ -172,6 +188,51 @@ ruleTester.run("prefer-destructuring", rule, {
                 type: "VariableDeclarator"
             }]
         },
+        {
+            code: "var foo = (a, b).foo;",
+            output: "var {foo} = (a, b);",
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var length = (() => {}).length;",
+            output: "var {length} = () => {};",
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var foo = (a = b).foo;",
+            output: "var {foo} = a = b;",
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var foo = (a || b).foo;",
+            output: "var {foo} = a || b;",
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var foo = (f()).foo;",
+            output: "var {foo} = f();",
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
         {
             code: "var foo = object.bar.foo;",
             output: "var {foo} = object.bar;",
@@ -221,6 +282,16 @@ ruleTester.run("prefer-destructuring", rule, {
                 type: "VariableDeclarator"
             }]
         },
+        {
+            code: "var foo = object[foo];",
+            output: null,
+            options: [{ object: true }, { enforceForRenamedProperties: true }],
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
         {
             code: "var foo = object['foo'];",
             output: null,
@@ -337,6 +408,224 @@ ruleTester.run("prefer-destructuring", rule, {
                 data: { type: "object" },
                 type: "VariableDeclarator"
             }]
+        },
+
+        // comments
+        {
+            code: "var /* comment */ foo = object.foo;",
+            output: "var /* comment */ {foo} = object;",
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var a, /* comment */foo = object.foo;",
+            output: "var a, /* comment */{foo} = object;",
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var foo /* comment */ = object.foo;",
+            output: null,
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var a, foo /* comment */ = object.foo;",
+            output: null,
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var foo /* comment */ = object.foo, a;",
+            output: null,
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var foo // comment\n = object.foo;",
+            output: null,
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var foo = /* comment */ object.foo;",
+            output: null,
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var foo = // comment\n object.foo;",
+            output: null,
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var foo = (/* comment */ object).foo;",
+            output: null,
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var foo = (object /* comment */).foo;",
+            output: null,
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var foo = bar(/* comment */).foo;",
+            output: "var {foo} = bar(/* comment */);",
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var foo = bar/* comment */.baz.foo;",
+            output: "var {foo} = bar/* comment */.baz;",
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var foo = bar[// comment\nbaz].foo;",
+            output: "var {foo} = bar[// comment\nbaz];",
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var foo // comment\n = bar(/* comment */).foo;",
+            output: null,
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var foo = bar/* comment */.baz/* comment */.foo;",
+            output: null,
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var foo = object// comment\n.foo;",
+            output: null,
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var foo = object./* comment */foo;",
+            output: null,
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var foo = (/* comment */ object.foo);",
+            output: null,
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var foo = (object.foo /* comment */);",
+            output: null,
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var foo = object.foo/* comment */;",
+            output: "var {foo} = object/* comment */;",
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var foo = object.foo// comment",
+            output: "var {foo} = object// comment",
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var foo = object.foo/* comment */, a;",
+            output: "var {foo} = object/* comment */, a;",
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var foo = object.foo// comment\n, a;",
+            output: "var {foo} = object// comment\n, a;",
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
+        },
+        {
+            code: "var foo = object.foo, /* comment */ a;",
+            output: "var {foo} = object, /* comment */ a;",
+            errors: [{
+                messageId: "preferDestructuring",
+                data: { type: "object" },
+                type: "VariableDeclarator"
+            }]
         }
     ]
 });
index 42bf8ec047deee058c1245b54e78ec375efb176c..da147b021fdf6d4c9903cf07f3ee258ae914f278 100644 (file)
@@ -40,7 +40,7 @@ function invalid(code, output) {
 // Tests
 //------------------------------------------------------------------------------
 
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } });
 
 ruleTester.run("prefer-exponentiation-operator", rule, {
     valid: [
@@ -346,6 +346,13 @@ ruleTester.run("prefer-exponentiation-operator", rule, {
         invalid("Math.pow(a, b/**/)", null),
         invalid("Math.pow(a, b//\n)", null),
         invalid("Math.pow(a, b)/* comment */;", "a**b/* comment */;"),
-        invalid("Math.pow(a, b)// comment\n;", "a**b// comment\n;")
+        invalid("Math.pow(a, b)// comment\n;", "a**b// comment\n;"),
+
+        // Optional chaining
+        invalid("Math.pow?.(a, b)", "a**b"),
+        invalid("Math?.pow(a, b)", "a**b"),
+        invalid("Math?.pow?.(a, b)", "a**b"),
+        invalid("(Math?.pow)(a, b)", "a**b"),
+        invalid("(Math?.pow)?.(a, b)", "a**b")
     ]
 });
index d8ce4117140dfb1063220a772617e1f28f7a592c..39cc11a242551fc9ddcdf50481724a88205aa867 100644 (file)
@@ -16,7 +16,7 @@ const rule = require("../../../lib/rules/prefer-numeric-literals"),
 // Tests
 //------------------------------------------------------------------------------
 
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2021 } });
 
 ruleTester.run("prefer-numeric-literals", rule, {
     valid: [
@@ -318,6 +318,57 @@ ruleTester.run("prefer-numeric-literals", rule, {
             code: "parseInt('11', 2)//comment\n;",
             output: "0b11//comment\n;",
             errors: 1
+        },
+
+        // Optional chaining
+        {
+            code: "parseInt?.(\"1F7\", 16) === 255;",
+            output: "0x1F7 === 255;",
+            errors: [{ message: "Use hexadecimal literals instead of parseInt()." }]
+        },
+        {
+            code: "Number?.parseInt(\"1F7\", 16) === 255;",
+            output: "0x1F7 === 255;",
+            errors: [{ message: "Use hexadecimal literals instead of Number?.parseInt()." }]
+        },
+        {
+            code: "Number?.parseInt?.(\"1F7\", 16) === 255;",
+            output: "0x1F7 === 255;",
+            errors: [{ message: "Use hexadecimal literals instead of Number?.parseInt()." }]
+        },
+        {
+            code: "(Number?.parseInt)(\"1F7\", 16) === 255;",
+            output: "0x1F7 === 255;",
+            errors: [{ message: "Use hexadecimal literals instead of Number?.parseInt()." }]
+        },
+        {
+            code: "(Number?.parseInt)?.(\"1F7\", 16) === 255;",
+            output: "0x1F7 === 255;",
+            errors: [{ message: "Use hexadecimal literals instead of Number?.parseInt()." }]
+        },
+
+        // `parseInt` doesn't support numeric separators. The rule shouldn't autofix in those cases.
+        {
+            code: "parseInt('1_0', 2);",
+            output: null,
+            errors: [{ message: "Use binary literals instead of parseInt()." }]
+        },
+        {
+            code: "Number.parseInt('5_000', 8);",
+            output: null,
+            errors: [{ message: "Use octal literals instead of Number.parseInt()." }]
+        },
+        {
+            code: "parseInt('0_1', 16);",
+            output: null,
+            errors: [{ message: "Use hexadecimal literals instead of parseInt()." }]
+        },
+        {
+
+            // this would be indeed the same as `0x0_0`, but there's no need to autofix this edge case that looks more like a mistake.
+            code: "Number.parseInt('0_0', 16);",
+            output: null,
+            errors: [{ message: "Use hexadecimal literals instead of Number.parseInt()." }]
         }
     ]
 });
index 012e44ce7a5ce165f55835d20dc41a0ecf7a509b..b31ec33622b20e0f1fca98b0da4b883662a6167a 100644 (file)
@@ -16,7 +16,7 @@ const { RuleTester } = require("../../../lib/rule-tester");
 // Tests
 //------------------------------------------------------------------------------
 
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 8 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2021 } });
 
 ruleTester.run("prefer-promise-reject-errors", rule, {
 
@@ -29,6 +29,8 @@ ruleTester.run("prefer-promise-reject-errors", rule, {
         "Promise.reject(new Error())",
         "Promise.reject(new TypeError)",
         "Promise.reject(new Error('foo'))",
+        "Promise.reject(foo || 5)",
+        "Promise.reject(5 && foo)",
         "new Foo((resolve, reject) => reject(5))",
         "new Promise(function(resolve, reject) { return function(reject) { reject(5) } })",
         "new Promise(function(resolve, reject) { if (foo) { const reject = somethingElse; reject(5) } })",
@@ -42,7 +44,17 @@ ruleTester.run("prefer-promise-reject-errors", rule, {
         {
             code: "new Promise(function(resolve, reject) { reject() })",
             options: [{ allowEmptyReject: true }]
-        }
+        },
+
+        // Optional chaining
+        "Promise.reject(obj?.foo)",
+        "Promise.reject(obj?.foo())",
+
+        // Assignments
+        "Promise.reject(foo = new Error())",
+        "Promise.reject(foo ||= 5)",
+        "Promise.reject(foo.bar ??= 5)",
+        "Promise.reject(foo[bar] ??= 5)"
     ],
 
     invalid: [
@@ -87,7 +99,27 @@ ruleTester.run("prefer-promise-reject-errors", rule, {
         "new Promise((foo, arguments) => arguments(5))",
         "new Promise(function({}, reject) { reject(5) })",
         "new Promise(({}, reject) => reject(5))",
-        "new Promise((resolve, reject, somethingElse = reject(5)) => {})"
+        "new Promise((resolve, reject, somethingElse = reject(5)) => {})",
+
+        // Optional chaining
+        "Promise.reject?.(5)",
+        "Promise?.reject(5)",
+        "Promise?.reject?.(5)",
+        "(Promise?.reject)(5)",
+        "(Promise?.reject)?.(5)",
+
+        // Assignments with mathematical operators will either evaluate to a primitive value or throw a TypeError
+        "Promise.reject(foo += new Error())",
+        "Promise.reject(foo -= new Error())",
+        "Promise.reject(foo **= new Error())",
+        "Promise.reject(foo <<= new Error())",
+        "Promise.reject(foo |= new Error())",
+        "Promise.reject(foo &= new Error())",
+
+        // evaluates either to a falsy value of `foo` (which, then, cannot be an Error object), or to `5`
+        "Promise.reject(foo && 5)",
+        "Promise.reject(foo &&= 5)"
+
     ].map(invalidCase => {
         const errors = { errors: [{ messageId: "rejectAnError", type: "CallExpression" }] };
 
index 65979de83f92c1f3f907b012851a41390a5b12fa..0ddaa8dae3df928aa4ba8a42292f59348fe13a1d 100644 (file)
@@ -16,13 +16,14 @@ const { RuleTester } = require("../../../lib/rule-tester");
 // Tests
 //------------------------------------------------------------------------------
 
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } });
 
 ruleTester.run("prefer-regex-literals", rule, {
     valid: [
         "/abc/",
         "/abc/g",
 
+
         // considered as dynamic
         "new RegExp(pattern)",
         "RegExp(pattern, 'g')",
@@ -41,6 +42,26 @@ ruleTester.run("prefer-regex-literals", rule, {
         "new RegExp(String.raw`a${''}c`);",
         "new RegExp('a' + 'b')",
         "RegExp(1)",
+        "new RegExp(/a/, 'u');",
+        "new RegExp(/a/);",
+        {
+            code: "new RegExp(/a/, flags);",
+            options: [{ disallowRedundantWrapping: true }]
+        },
+        {
+            code: "new RegExp(/a/, `u${flags}`);",
+            options: [{ disallowRedundantWrapping: true }]
+        },
+
+        // redundant wrapping is allowed
+        {
+            code: "new RegExp(/a/);",
+            options: [{}]
+        },
+        {
+            code: "new RegExp(/a/);",
+            options: [{ disallowRedundantWrapping: false }]
+        },
 
         // invalid number of arguments
         "new RegExp;",
@@ -52,6 +73,10 @@ ruleTester.run("prefer-regex-literals", rule, {
         "RegExp(`a`, `g`, `b`);",
         "new RegExp(String.raw`a`, String.raw`g`, String.raw`b`);",
         "RegExp(String.raw`a`, String.raw`g`, String.raw`b`);",
+        {
+            code: "new RegExp(/a/, 'u', 'foo');",
+            options: [{ disallowRedundantWrapping: true }]
+        },
 
         // not String.raw``
         "new RegExp(String`a`);",
@@ -196,6 +221,33 @@ ruleTester.run("prefer-regex-literals", rule, {
             code: "globalThis.RegExp('a');",
             env: { es2020: true },
             errors: [{ messageId: "unexpectedRegExp", type: "CallExpression" }]
+        },
+
+        {
+            code: "new RegExp(/a/);",
+            options: [{ disallowRedundantWrapping: true }],
+            errors: [{ messageId: "unexpectedRedundantRegExp", type: "NewExpression", line: 1, column: 1 }]
+        },
+        {
+            code: "new RegExp(/a/, 'u');",
+            options: [{ disallowRedundantWrapping: true }],
+            errors: [{ messageId: "unexpectedRedundantRegExpWithFlags", type: "NewExpression", line: 1, column: 1 }]
+        },
+        {
+            code: "new RegExp(/a/, `u`);",
+            options: [{ disallowRedundantWrapping: true }],
+            errors: [{ messageId: "unexpectedRedundantRegExpWithFlags", type: "NewExpression", line: 1, column: 1 }]
+        },
+        {
+            code: "new RegExp('a');",
+            options: [{ disallowRedundantWrapping: true }],
+            errors: [{ messageId: "unexpectedRegExp", type: "NewExpression", line: 1, column: 1 }]
+        },
+
+        // Optional chaining
+        {
+            code: "new RegExp((String?.raw)`a`);",
+            errors: [{ messageId: "unexpectedRegExp" }]
         }
     ]
 });
index 580d7faeba9d8125a173528c3415632c951704bf..7f48d845f1b43c2d0dfafb70509ac2ac74aa0a94 100644 (file)
@@ -18,7 +18,7 @@ const { RuleTester } = require("../../../lib/rule-tester");
 
 const errors = [{ messageId: "preferSpread", type: "CallExpression" }];
 
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } });
 
 ruleTester.run("prefer-spread", rule, {
     valid: [
@@ -39,7 +39,11 @@ ruleTester.run("prefer-spread", rule, {
         // ignores incomplete things.
         "foo.apply();",
         "obj.foo.apply();",
-        "obj.foo.apply(obj, ...args)"
+        "obj.foo.apply(obj, ...args)",
+
+        // Optional chaining
+        "(a?.b).c.foo.apply(a?.b.c, args);",
+        "a?.b.c.foo.apply((a?.b).c, args);"
     ],
     invalid: [
         {
@@ -73,6 +77,44 @@ ruleTester.run("prefer-spread", rule, {
         {
             code: "[].concat.apply([\n/*empty*/\n], args);",
             errors
+        },
+
+        // Optional chaining
+        {
+            code: "foo.apply?.(undefined, args);",
+            errors
+        },
+        {
+            code: "foo?.apply(undefined, args);",
+            errors
+        },
+        {
+            code: "foo?.apply?.(undefined, args);",
+            errors
+        },
+        {
+            code: "(foo?.apply)(undefined, args);",
+            errors
+        },
+        {
+            code: "(foo?.apply)?.(undefined, args);",
+            errors
+        },
+        {
+            code: "(obj?.foo).apply(obj, args);",
+            errors
+        },
+        {
+            code: "a?.b.c.foo.apply(a?.b.c, args);",
+            errors
+        },
+        {
+            code: "(a?.b.c).foo.apply(a?.b.c, args);",
+            errors
+        },
+        {
+            code: "(a?.b).c.foo.apply((a?.b).c, args);",
+            errors
         }
     ]
 });
index 3ac3879d636d5b51d1b0c0c66af344b329e8e0ea..eea0255f1ac0454bcbaa455f92fe94c14fd10f1e 100644 (file)
@@ -194,6 +194,11 @@ ruleTester.run("prefer-template", rule, {
             output: null,
             errors
         },
+        {
+            code: "foo + 'does not autofix non-octal decimal escape sequence' + '\\8'",
+            output: null,
+            errors
+        },
         {
             code: "foo + '\\n other text \\033'",
             output: null,
index 136e12fea9ee40223a9ee45dfd1a41264bac68a2..5186f87d140a85b9bf7d4969bbb0de7a99ca04a9 100644 (file)
@@ -79,7 +79,13 @@ ruleTester.run("quote-props", rule, {
         { code: "({ 1n: 1 })", options: ["consistent"], parserOptions: { ecmaVersion: 2020 } },
         { code: "({ 1n: 1 })", options: ["consistent-as-needed"], parserOptions: { ecmaVersion: 2020 } },
         { code: "({ '99999999999999999': 1 })", options: ["as-needed"], parserOptions: { ecmaVersion: 2020 } },
-        { code: "({ '1n': 1 })", options: ["as-needed"], parserOptions: { ecmaVersion: 2020 } }
+        { code: "({ '1n': 1 })", options: ["as-needed"], parserOptions: { ecmaVersion: 2020 } },
+        { code: "({ 1_0: 1 })", options: ["as-needed"], parserOptions: { ecmaVersion: 2021 } },
+        { code: "({ 1_0: 1 })", options: ["as-needed", { numbers: false }], parserOptions: { ecmaVersion: 2021 } },
+        { code: "({ '1_0': 1 })", options: ["as-needed"], parserOptions: { ecmaVersion: 2021 } },
+        { code: "({ '1_0': 1 })", options: ["as-needed", { numbers: false }], parserOptions: { ecmaVersion: 2021 } },
+        { code: "({ '1_0': 1 })", options: ["as-needed", { numbers: true }], parserOptions: { ecmaVersion: 2021 } },
+        { code: "({ 1_0: 1, 1: 1 })", options: ["consistent-as-needed"], parserOptions: { ecmaVersion: 2021 } }
     ],
     invalid: [{
         code: "({ a: 0 })",
@@ -380,5 +386,41 @@ ruleTester.run("quote-props", rule, {
             messageId: "unquotedNumericProperty",
             data: { property: "1" }
         }]
+    }, {
+        code: "({ 1_0: 1 })",
+        output: "({ \"10\": 1 })",
+        options: ["as-needed", { numbers: true }],
+        parserOptions: { ecmaVersion: 2021 },
+        errors: [{
+            messageId: "unquotedNumericProperty",
+            data: { property: "10" }
+        }]
+    }, {
+        code: "({ 1_2.3_4e0_2: 1 })",
+        output: "({ \"1234\": 1 })",
+        options: ["always"],
+        parserOptions: { ecmaVersion: 2021 },
+        errors: [{
+            messageId: "unquotedPropertyFound",
+            data: { property: "1234" }
+        }]
+    }, {
+        code: "({ 0b1_000: 1 })",
+        output: "({ \"8\": 1 })",
+        options: ["always"],
+        parserOptions: { ecmaVersion: 2021 },
+        errors: [{
+            messageId: "unquotedPropertyFound",
+            data: { property: "8" }
+        }]
+    }, {
+        code: "({ 1_000: a, '1_000': b })",
+        output: "({ \"1000\": a, '1_000': b })",
+        options: ["consistent-as-needed"],
+        parserOptions: { ecmaVersion: 2021 },
+        errors: [{
+            messageId: "inconsistentlyQuotedProperty",
+            data: { key: "1000" }
+        }]
     }]
 });
index 86dc711f7943162a11f96287155a834381598b73..a7e223aab9b360ab88ef69d8c5e7619f4b9285fb 100644 (file)
@@ -614,6 +614,19 @@ ruleTester.run("quotes", rule, {
                     type: "Literal"
                 }
             ]
+        },
+        {
+            code: "var nonOctalDecimalEscape = '\\8'",
+            output: null,
+            options: ["backtick"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "wrongQuotes",
+                    data: { description: "backtick" },
+                    type: "Literal"
+                }
+            ]
         }
     ]
 });
index 2766e80b597d17f4086f255a02bfa01b44bfe1aa..52801c3d18313b7d218826412bd75c9b7061f36a 100644 (file)
@@ -185,6 +185,28 @@ ruleTester.run("radix", rule, {
                 messageId: "redundantRadix",
                 type: "CallExpression"
             }]
+        },
+
+        // Optional chaining
+        {
+            code: "parseInt?.(\"10\");",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "missingRadix" }]
+        },
+        {
+            code: "Number.parseInt?.(\"10\");",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "missingRadix" }]
+        },
+        {
+            code: "Number?.parseInt(\"10\");",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "missingRadix" }]
+        },
+        {
+            code: "(Number?.parseInt)(\"10\");",
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "missingRadix" }]
         }
     ]
 });
index 6582a5ca690e90e8b873a76f0502f09a5d5a76a0..b01dac04b158c50eb19cbe9c8649d31d260815b7 100644 (file)
@@ -55,7 +55,9 @@ ruleTester.run("semi-spacing", rule, {
         "function foo(){return 2;}",
         "for(var i = 0; i < results.length;) {}",
         { code: "function foo() { return 2; }", options: [{ after: false }] },
-        { code: "for ( var i = 0;i < results.length; ) {}", options: [{ after: false }] }
+        { code: "for ( var i = 0;i < results.length; ) {}", options: [{ after: false }] },
+
+        "do {} while (true); foo"
     ],
     invalid: [
         {
@@ -370,6 +372,51 @@ ruleTester.run("semi-spacing", rule, {
                 endColumn: 7
             }
             ]
+        },
+        {
+            code: "do {} while (true) ;",
+            output: "do {} while (true);",
+            errors: [{
+                messageId: "unexpectedWhitespaceBefore",
+                type: "DoWhileStatement",
+                line: 1,
+                column: 19,
+                endLine: 1,
+                endColumn: 20
+            }]
+        },
+        {
+            code: "do {} while (true);foo",
+            output: "do {} while (true); foo",
+            errors: [{
+                messageId: "missingWhitespaceAfter",
+                type: "DoWhileStatement",
+                line: 1,
+                column: 19,
+                endLine: 1,
+                endColumn: 20
+            }]
+        },
+        {
+            code: "do {} while (true);  foo",
+            output: "do {} while (true) ;foo",
+            options: [{ before: true, after: false }],
+            errors: [{
+                messageId: "missingWhitespaceBefore",
+                type: "DoWhileStatement",
+                line: 1,
+                column: 19,
+                endLine: 1,
+                endColumn: 20
+            },
+            {
+                messageId: "unexpectedWhitespaceAfter",
+                type: "DoWhileStatement",
+                line: 1,
+                column: 20,
+                endLine: 1,
+                endColumn: 22
+            }]
         }
     ]
 });
index 51f3ee3a804511f6d35d925d9e426837f6c42269..b5da50189192e0188139cd519bf1253abfc9b477 100644 (file)
@@ -101,7 +101,45 @@ ruleTester.run("sort-imports", rule, {
         },
 
         // https://github.com/eslint/eslint/issues/5305
-        "import React, {Component} from 'react';"
+        "import React, {Component} from 'react';",
+
+        // allowSeparatedGroups
+        {
+            code: "import b from 'b';\n\nimport a from 'a';",
+            options: [{ allowSeparatedGroups: true }]
+        },
+        {
+            code: "import a from 'a';\n\nimport 'b';",
+            options: [{ allowSeparatedGroups: true }]
+        },
+        {
+            code: "import { b } from 'b';\n\n\nimport { a } from 'a';",
+            options: [{ allowSeparatedGroups: true }]
+        },
+        {
+            code: "import b from 'b';\n// comment\nimport a from 'a';",
+            options: [{ allowSeparatedGroups: true }]
+        },
+        {
+            code: "import b from 'b';\nfoo();\nimport a from 'a';",
+            options: [{ allowSeparatedGroups: true }]
+        },
+        {
+            code: "import { b } from 'b';/*\n comment \n*/import { a } from 'a';",
+            options: [{ allowSeparatedGroups: true }]
+        },
+        {
+            code: "import b from\n'b';\n\nimport\n a from 'a';",
+            options: [{ allowSeparatedGroups: true }]
+        },
+        {
+            code: "import c from 'c';\n\nimport a from 'a';\nimport b from 'b';",
+            options: [{ allowSeparatedGroups: true }]
+        },
+        {
+            code: "import c from 'c';\n\nimport b from 'b';\n\nimport a from 'a';",
+            options: [{ allowSeparatedGroups: true }]
+        }
     ],
     invalid: [
         {
@@ -287,6 +325,125 @@ ruleTester.run("sort-imports", rule, {
                 data: { memberName: "qux" },
                 type: "ImportSpecifier"
             }]
+        },
+
+        // allowSeparatedGroups
+        {
+            code: "import b from 'b';\nimport a from 'a';",
+            output: null,
+            errors: [{
+                messageId: "sortImportsAlphabetically",
+                type: "ImportDeclaration"
+            }]
+        },
+        {
+            code: "import b from 'b';\nimport a from 'a';",
+            output: null,
+            options: [{}],
+            errors: [{
+                messageId: "sortImportsAlphabetically",
+                type: "ImportDeclaration"
+            }]
+        },
+        {
+            code: "import b from 'b';\nimport a from 'a';",
+            output: null,
+            options: [{ allowSeparatedGroups: false }],
+            errors: [{
+                messageId: "sortImportsAlphabetically",
+                type: "ImportDeclaration"
+            }]
+        },
+        {
+            code: "import b from 'b';import a from 'a';",
+            output: null,
+            options: [{ allowSeparatedGroups: false }],
+            errors: [{
+                messageId: "sortImportsAlphabetically",
+                type: "ImportDeclaration"
+            }]
+        },
+        {
+            code: "import b from 'b'; /* comment */ import a from 'a';",
+            output: null,
+            options: [{ allowSeparatedGroups: false }],
+            errors: [{
+                messageId: "sortImportsAlphabetically",
+                type: "ImportDeclaration"
+            }]
+        },
+        {
+            code: "import b from 'b'; // comment\nimport a from 'a';",
+            output: null,
+            options: [{ allowSeparatedGroups: false }],
+            errors: [{
+                messageId: "sortImportsAlphabetically",
+                type: "ImportDeclaration"
+            }]
+        },
+        {
+            code: "import b from 'b'; // comment 1\n/* comment 2 */import a from 'a';",
+            output: null,
+            options: [{ allowSeparatedGroups: false }],
+            errors: [{
+                messageId: "sortImportsAlphabetically",
+                type: "ImportDeclaration"
+            }]
+        },
+        {
+            code: "import { b } from 'b'; /* comment line 1 \n comment line 2 */ import { a } from 'a';",
+            output: null,
+            options: [{ allowSeparatedGroups: false }],
+            errors: [{
+                messageId: "sortImportsAlphabetically",
+                type: "ImportDeclaration"
+            }]
+        },
+        {
+            code: "import b\nfrom 'b'; import a\nfrom 'a';",
+            output: null,
+            options: [{ allowSeparatedGroups: false }],
+            errors: [{
+                messageId: "sortImportsAlphabetically",
+                type: "ImportDeclaration"
+            }]
+        },
+        {
+            code: "import { b } from \n'b'; /* comment */ import\n { a } from 'a';",
+            output: null,
+            options: [{ allowSeparatedGroups: false }],
+            errors: [{
+                messageId: "sortImportsAlphabetically",
+                type: "ImportDeclaration"
+            }]
+        },
+        {
+            code: "import { b } from \n'b';\nimport\n { a } from 'a';",
+            output: null,
+            options: [{ allowSeparatedGroups: false }],
+            errors: [{
+                messageId: "sortImportsAlphabetically",
+                type: "ImportDeclaration"
+            }]
+        },
+        {
+            code: "import c from 'c';\n\nimport b from 'b';\nimport a from 'a';",
+            output: null,
+            options: [{ allowSeparatedGroups: true }],
+            errors: [{
+                messageId: "sortImportsAlphabetically",
+                type: "ImportDeclaration",
+                line: 4
+            }]
+        },
+        {
+            code: "import b from 'b';\n\nimport { c, a } from 'c';",
+            output: "import b from 'b';\n\nimport { a, c } from 'c';",
+            options: [{ allowSeparatedGroups: true }],
+            errors: [{
+                messageId: "sortMembersAlphabetically",
+                type: "ImportSpecifier"
+            }]
         }
     ]
 });
index 7b36d9f30ee34588f80e70e098811d279a728f9c..7424aec84584a06d272a7aa0d85f1a5e295333c2 100644 (file)
@@ -10,7 +10,8 @@
 //------------------------------------------------------------------------------
 
 const rule = require("../../../lib/rules/space-before-blocks"),
-    { RuleTester } = require("../../../lib/rule-tester");
+    { RuleTester } = require("../../../lib/rule-tester"),
+    fixtureParser = require("../../fixtures/fixture-parser");
 
 //------------------------------------------------------------------------------
 // Tests
@@ -555,6 +556,21 @@ ruleTester.run("space-before-blocks", rule, {
             options: classesNeverOthersOffArgs,
             parserOptions: { ecmaVersion: 6 },
             errors: [expectedNoSpacingError]
+        },
+
+        // https://github.com/eslint/eslint/issues/13553
+        {
+            code: "class A { foo(bar: string): void{} }",
+            output: "class A { foo(bar: string): void {} }",
+            parser: fixtureParser("space-before-blocks", "return-type-keyword-1"),
+            errors: [expectedSpacingError]
+        },
+        {
+            code: "function foo(): null {}",
+            output: "function foo(): null{}",
+            options: neverArgs,
+            parser: fixtureParser("space-before-blocks", "return-type-keyword-2"),
+            errors: [expectedNoSpacingError]
         }
     ]
 });
index 65c1e562002f03775a3301d6ae04c39545d46a54..2e9edbaa8cfd6f2bb88456f8b9d348196794b342 100644 (file)
@@ -47,7 +47,11 @@ ruleTester.run("space-infix-ops", rule, {
         { code: "const foo = function(a: number = 0): Bar { };", parser: parser("type-annotations/function-expression-type-annotation"), parserOptions: { ecmaVersion: 6 } },
 
         // TypeScript Type Aliases
-        { code: "type Foo<T> = T;", parser: parser("typescript-parsers/type-alias"), parserOptions: { ecmaVersion: 6 } }
+        { code: "type Foo<T> = T;", parser: parser("typescript-parsers/type-alias"), parserOptions: { ecmaVersion: 6 } },
+
+        { code: "a &&= b", parserOptions: { ecmaVersion: 2021 } },
+        { code: "a ||= b", parserOptions: { ecmaVersion: 2021 } },
+        { code: "a ??= b", parserOptions: { ecmaVersion: 2021 } }
     ],
     invalid: [
         {
@@ -408,7 +412,6 @@ ruleTester.run("space-infix-ops", rule, {
         },
 
         // Type Annotations
-
         {
             code: "var a: Foo= b;",
             output: "var a: Foo = b;",
@@ -433,6 +436,49 @@ ruleTester.run("space-infix-ops", rule, {
                 column: 23,
                 type: "AssignmentPattern"
             }]
+        },
+
+        {
+            code: "a&&=b",
+            output: "a &&= b",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{
+                messageId: "missingSpace",
+                data: { operator: "&&=" },
+                line: 1,
+                column: 2,
+                endLine: 1,
+                endColumn: 5,
+                type: "AssignmentExpression"
+            }]
+        },
+        {
+            code: "a ||=b",
+            output: "a ||= b",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{
+                messageId: "missingSpace",
+                data: { operator: "||=" },
+                line: 1,
+                column: 3,
+                endLine: 1,
+                endColumn: 6,
+                type: "AssignmentExpression"
+            }]
+        },
+        {
+            code: "a??= b",
+            output: "a ??= b",
+            parserOptions: { ecmaVersion: 2021 },
+            errors: [{
+                messageId: "missingSpace",
+                data: { operator: "??=" },
+                line: 1,
+                column: 2,
+                endLine: 1,
+                endColumn: 5,
+                type: "AssignmentExpression"
+            }]
         }
     ]
 });
index aa1b1746fcdf03ee802eb057f2c629816b4ad8ed..c5a4b6bd6863e368f6c6bf8e23a68703bcb4de38 100644 (file)
@@ -385,6 +385,24 @@ ruleTester.run("use-isnan", rule, {
             code: "foo.bar.lastIndexOf(NaN)",
             options: [{ enforceForIndexOf: true }],
             errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "lastIndexOf" } }]
+        },
+        {
+            code: "foo.indexOf?.(NaN)",
+            options: [{ enforceForIndexOf: true }],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }]
+        },
+        {
+            code: "foo?.indexOf(NaN)",
+            options: [{ enforceForIndexOf: true }],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }]
+        },
+        {
+            code: "(foo?.indexOf)(NaN)",
+            options: [{ enforceForIndexOf: true }],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }]
         }
     ]
 });
index 683e5ed2a6e337b6c4e149152148c8e940d3e4a4..d0ad2748431634fbee4c26f71ad37b5ad1a01eab 100644 (file)
@@ -10,6 +10,7 @@
 //------------------------------------------------------------------------------
 
 const assert = require("chai").assert,
+    util = require("util"),
     espree = require("espree"),
     astUtils = require("../../../../lib/rules/utils/ast-utils"),
     { Linter } = require("../../../../lib/linter"),
@@ -478,6 +479,28 @@ describe("ast-utils", () => {
                 assert.strictEqual(astUtils.getStaticStringValue(ast.body[0].expression), expectedResults[key]);
             });
         });
+
+        it("should return text of regex literal even if it's not supported natively.", () => {
+            const node = {
+                type: "Literal",
+                value: null,
+                regex: { pattern: "(?:)", flags: "u" }
+            };
+            const expectedText = "/(?:)/u";
+
+            assert.strictEqual(astUtils.getStaticStringValue(node), expectedText);
+        });
+
+        it("should return text of bigint literal even if it's not supported natively.", () => {
+            const node = {
+                type: "Literal",
+                value: null,
+                bigint: "100n"
+            };
+            const expectedText = "100n";
+
+            assert.strictEqual(astUtils.getStaticStringValue(node), expectedText);
+        });
     });
 
     describe("getStaticPropertyName", () => {
@@ -714,21 +737,84 @@ describe("ast-utils", () => {
 
     {
         const expectedResults = {
-            5: true,
             0: true,
+            5: true,
+            8: true,
+            9: true,
+            50: true,
+            123: true,
+            "1_0": true,
+            "1_0_1": true,
+            "12_3": true,
+            "5_000": true,
+            "500_0": true,
+            "500_00": true,
+            "5_000_00": true,
+            "1_234_56": true,
+            "1_2_3_4": true,
+            "11_22_33_44": true,
+            "1_23_4_56_7_89": true,
+            "08": true,
+            "09": true,
+            "008": true,
+            "0192": true,
+            "0180": true,
+            "090": true,
+            "088": true,
+            "099": true,
+            "089": true,
+            "0098": true,
+            "01892": true,
+            "08192": true,
+            "01829": true,
+            "018290": true,
+            "0.": false,
             "5.": false,
+            ".0": false,
+            ".5": false,
             "5.0": false,
+            "5.00_00": false,
+            "5.0_1": false,
+            "0.1_0": false,
+            "5.1_2": false,
+            "1.23_45": false,
+            ".0_1": false,
+            ".12_34": false,
             "05": false,
+            "0123": false,
+            "076543210": false,
+            "08.": false,
             "0x5": false,
+            "0b11_01": false,
+            "0o0_1": false,
+            "0x56_78": false,
             "5e0": false,
+            "0.e1": false,
+            ".0e1": false,
+            "5e0_1": false,
+            "5e1_000": false,
+            "5e12_34": false,
             "5e-0": false,
+            "5e-0_1": false,
+            "5e-1_2": false,
+            "1_2.3_4e5_6": false,
+            "1n": false,
+            "1_2n": false,
+            "1_000n": false,
             "'5'": false
         };
 
+        const ecmaVersion = espree.latestEcmaVersion;
+
         describe("isDecimalInteger", () => {
             Object.keys(expectedResults).forEach(key => {
                 it(`should return ${expectedResults[key]} for ${key}`, () => {
-                    assert.strictEqual(astUtils.isDecimalInteger(espree.parse(key).body[0].expression), expectedResults[key]);
+                    assert.strictEqual(
+                        astUtils.isDecimalInteger(
+                            espree.parse(key, { ecmaVersion }).body[0].expression
+                        ),
+                        expectedResults[key]
+                    );
                 });
             });
         });
@@ -736,7 +822,12 @@ describe("ast-utils", () => {
         describe("isDecimalIntegerNumericToken", () => {
             Object.keys(expectedResults).forEach(key => {
                 it(`should return ${expectedResults[key]} for ${key}`, () => {
-                    assert.strictEqual(astUtils.isDecimalIntegerNumericToken(espree.tokenize(key)[0]), expectedResults[key]);
+                    assert.strictEqual(
+                        astUtils.isDecimalIntegerNumericToken(
+                            espree.tokenize(key, { ecmaVersion })[0]
+                        ),
+                        expectedResults[key]
+                    );
                 });
             });
         });
@@ -991,12 +1082,30 @@ describe("ast-utils", () => {
             "foo.bar": true,
             "(foo = bar)": true,
             "(foo = 1)": false,
+            "(foo += bar)": false,
+            "(foo -= bar)": false,
+            "(foo *= bar)": false,
+            "(foo /= bar)": false,
+            "(foo %= bar)": false,
+            "(foo **= bar)": false,
+            "(foo <<= bar)": false,
+            "(foo >>= bar)": false,
+            "(foo >>>= bar)": false,
+            "(foo &= bar)": false,
+            "(foo |= bar)": false,
+            "(foo ^= bar)": false,
             "(1, 2, 3)": false,
             "(foo, 2, 3)": false,
             "(1, 2, foo)": true,
             "1 && 2": false,
             "1 && foo": true,
-            "foo && 2": true,
+            "foo && 2": false,
+
+            // A future improvement could detect the left side as statically falsy, making this false.
+            "false && foo": true,
+            "foo &&= 2": false,
+            "foo.bar ??= 2": true,
+            "foo[bar] ||= 2": true,
             "foo ? 1 : 2": false,
             "foo ? bar : 2": true,
             "foo ? 1 : bar": true,
@@ -1006,7 +1115,7 @@ describe("ast-utils", () => {
 
         Object.keys(EXPECTED_RESULTS).forEach(key => {
             it(`returns ${EXPECTED_RESULTS[key]} for ${key}`, () => {
-                const ast = espree.parse(key, { ecmaVersion: 6 });
+                const ast = espree.parse(key, { ecmaVersion: 2021 });
 
                 assert.strictEqual(astUtils.couldBeError(ast.body[0].expression), EXPECTED_RESULTS[key]);
             });
@@ -1411,7 +1520,135 @@ describe("ast-utils", () => {
         });
     });
 
-    describe("hasOctalEscapeSequence", () => {
+    describe("equalLiteralValue", () => {
+        describe("should return true if two regex values are same, even if it's not supported natively.", () => {
+            const patterns = [
+                {
+                    nodeA: {
+                        type: "Literal",
+                        value: /(?:)/u,
+                        regex: { pattern: "(?:)", flags: "u" }
+                    },
+                    nodeB: {
+                        type: "Literal",
+                        value: /(?:)/u,
+                        regex: { pattern: "(?:)", flags: "u" }
+                    },
+                    expected: true
+                },
+                {
+                    nodeA: {
+                        type: "Literal",
+                        value: null,
+                        regex: { pattern: "(?:)", flags: "u" }
+                    },
+                    nodeB: {
+                        type: "Literal",
+                        value: null,
+                        regex: { pattern: "(?:)", flags: "u" }
+                    },
+                    expected: true
+                },
+                {
+                    nodeA: {
+                        type: "Literal",
+                        value: null,
+                        regex: { pattern: "(?:)", flags: "u" }
+                    },
+                    nodeB: {
+                        type: "Literal",
+                        value: /(?:)/, // eslint-disable-line require-unicode-regexp
+                        regex: { pattern: "(?:)", flags: "" }
+                    },
+                    expected: false
+                },
+                {
+                    nodeA: {
+                        type: "Literal",
+                        value: null,
+                        regex: { pattern: "(?:a)", flags: "u" }
+                    },
+                    nodeB: {
+                        type: "Literal",
+                        value: null,
+                        regex: { pattern: "(?:b)", flags: "u" }
+                    },
+                    expected: false
+                }
+            ];
+
+            for (const { nodeA, nodeB, expected } of patterns) {
+                it(`should return ${expected} if it compared ${util.format("%o", nodeA)} and ${util.format("%o", nodeB)}`, () => {
+                    assert.strictEqual(astUtils.equalLiteralValue(nodeA, nodeB), expected);
+                });
+            }
+        });
+
+        describe("should return true if two bigint values are same, even if it's not supported natively.", () => {
+            const patterns = [
+                {
+                    nodeA: {
+                        type: "Literal",
+                        value: null,
+                        bigint: "1"
+                    },
+                    nodeB: {
+                        type: "Literal",
+                        value: null,
+                        bigint: "1"
+                    },
+                    expected: true
+                },
+                {
+                    nodeA: {
+                        type: "Literal",
+                        value: null,
+                        bigint: "1"
+                    },
+                    nodeB: {
+                        type: "Literal",
+                        value: null,
+                        bigint: "2"
+                    },
+                    expected: false
+                },
+                {
+                    nodeA: {
+                        type: "Literal",
+                        value: 1n,
+                        bigint: "1"
+                    },
+                    nodeB: {
+                        type: "Literal",
+                        value: 1n,
+                        bigint: "1"
+                    },
+                    expected: true
+                },
+                {
+                    nodeA: {
+                        type: "Literal",
+                        value: 1n,
+                        bigint: "1"
+                    },
+                    nodeB: {
+                        type: "Literal",
+                        value: 2n,
+                        bigint: "2"
+                    },
+                    expected: false
+                }
+            ];
+
+            for (const { nodeA, nodeB, expected } of patterns) {
+                it(`should return ${expected} if it compared ${util.format("%o", nodeA)} and ${util.format("%o", nodeB)}`, () => {
+                    assert.strictEqual(astUtils.equalLiteralValue(nodeA, nodeB), expected);
+                });
+            }
+        });
+    });
+
+    describe("hasOctalOrNonOctalDecimalEscapeSequence", () => {
 
         /* eslint-disable quote-props */
         const expectedResults = {
@@ -1446,19 +1683,24 @@ describe("ast-utils", () => {
             "\\\\\\1": true,
             "\\\\\\01": true,
             "\\\\\\08": true,
+            "\\8": true,
+            "\\9": true,
+            "a\\8a": true,
+            "\\0\\8": true,
+            "\\8\\0": true,
+            "\\80": true,
+            "\\81": true,
+            "\\\\\\8": true,
+            "\\\n\\1": true,
+            "foo\\\nbar\\2baz": true,
+            "\\\n\\8": true,
+            "foo\\\nbar\\9baz": true,
 
             "\\0": false,
-            "\\8": false,
-            "\\9": false,
             " \\0": false,
             "\\0 ": false,
             "a\\0": false,
             "\\0a": false,
-            "a\\8a": false,
-            "\\0\\8": false,
-            "\\8\\0": false,
-            "\\80": false,
-            "\\81": false,
             "\\\\": false,
             "\\\\0": false,
             "\\\\01": false,
@@ -1466,7 +1708,6 @@ describe("ast-utils", () => {
             "\\\\1": false,
             "\\\\12": false,
             "\\\\\\0": false,
-            "\\\\\\8": false,
             "\\0\\\\": false,
             "0": false,
             "1": false,
@@ -1476,7 +1717,10 @@ describe("ast-utils", () => {
             "80": false,
             "12": false,
             "\\a": false,
-            "\\n": false
+            "\\n": false,
+            "\\\n": false,
+            "foo\\\nbar": false,
+            "128\\\n349": false
         };
         /* eslint-enable quote-props */
 
@@ -1484,7 +1728,31 @@ describe("ast-utils", () => {
             it(`should return ${expectedResults[key]} for ${key}`, () => {
                 const ast = espree.parse(`"${key}"`);
 
-                assert.strictEqual(astUtils.hasOctalEscapeSequence(ast.body[0].expression.raw), expectedResults[key]);
+                assert.strictEqual(astUtils.hasOctalOrNonOctalDecimalEscapeSequence(ast.body[0].expression.raw), expectedResults[key]);
+            });
+        });
+    });
+
+    describe("isLogicalAssignmentOperator", () => {
+        const expectedResults = {
+            "&&=": true,
+            "||=": true,
+            "??=": true,
+            "&&": false,
+            "||": false,
+            "??": false,
+            "=": false,
+            "&=": false,
+            "|=": false,
+            "+=": false,
+            "**=": false,
+            "==": false,
+            "===": false
+        };
+
+        Object.entries(expectedResults).forEach(([key, value]) => {
+            it(`should return ${value} for ${key}`, () => {
+                assert.strictEqual(astUtils.isLogicalAssignmentOperator(key), value);
             });
         });
     });
index d18948f429adcb5d84cf4f627ca55e8e54839e6b..035e265da40420083ca994973f15aef30fe64930 100644 (file)
@@ -603,6 +603,36 @@ ruleTester.run("wrap-iife", rule, {
             options: ["inside", { functionPrototypeMethods: true }],
             parserOptions: { ecmaVersion: 2020 },
             errors: [wrapExpressionError]
+        },
+
+        // Optional chaining
+        {
+            code: "window.bar = function() { return 3; }.call?.(this, arg1);",
+            output: "window.bar = (function() { return 3; }).call?.(this, arg1);",
+            options: ["inside", { functionPrototypeMethods: true }],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [wrapInvocationError]
+        },
+        {
+            code: "window.bar = function() { return 3; }?.call(this, arg1);",
+            output: "window.bar = (function() { return 3; })?.call(this, arg1);",
+            options: ["inside", { functionPrototypeMethods: true }],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [wrapInvocationError]
+        },
+        {
+            code: "window.bar = (function() { return 3; }?.call)(this, arg1);",
+            output: "window.bar = ((function() { return 3; })?.call)(this, arg1);",
+            options: ["inside", { functionPrototypeMethods: true }],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [wrapInvocationError]
+        },
+        {
+            code: "new (function () {} ?.());",
+            output: "new ((function () {}) ?.());",
+            options: ["inside"],
+            parserOptions: { ecmaVersion: 2020 },
+            errors: [wrapExpressionError]
         }
     ]
 });
index 43449401873ea466f41b62907cbc33fb4fe9dc5b..7f88a829855ee9cc6568e3e33258d42587ba5a69 100644 (file)
@@ -288,6 +288,11 @@ ruleTester.run("yoda", rule, {
             code: "if('a' <= x && x < MAX) {}",
             options: ["never", { exceptRange: true }]
         },
+        {
+            code: "if (0 <= obj?.a && obj?.a < 1) {}",
+            options: ["never", { exceptRange: true }],
+            parserOptions: { ecmaVersion: 2020 }
+        },
 
         // onlyEquality
         {
@@ -1159,6 +1164,150 @@ ruleTester.run("yoda", rule, {
                 }
             ]
         },
+        {
+            code: "0 < f()in obj",
+            output: "f() > 0 in obj",
+            errors: [
+                {
+                    messageId: "expected",
+                    data: { expectedSide: "right", operator: "<" },
+                    type: "BinaryExpression"
+                }
+            ]
+        },
+        {
+            code: "1 > x++instanceof foo",
+            output: "x++ < 1 instanceof foo",
+            options: ["never"],
+            errors: [
+                {
+                    messageId: "expected",
+                    data: { expectedSide: "right", operator: ">" },
+                    type: "BinaryExpression"
+                }
+            ]
+        },
+        {
+            code: "x < ('foo')in bar",
+            output: "('foo') > x in bar",
+            options: ["always"],
+            errors: [
+                {
+                    messageId: "expected",
+                    data: { expectedSide: "left", operator: "<" },
+                    type: "BinaryExpression"
+                }
+            ]
+        },
+        {
+            code: "false <= ((x))in foo",
+            output: "((x)) >= false in foo",
+            options: ["never"],
+            errors: [
+                {
+                    messageId: "expected",
+                    data: { expectedSide: "right", operator: "<=" },
+                    type: "BinaryExpression"
+                }
+            ]
+        },
+        {
+            code: "x >= (1)instanceof foo",
+            output: "(1) <= x instanceof foo",
+            options: ["always"],
+            errors: [
+                {
+                    messageId: "expected",
+                    data: { expectedSide: "left", operator: ">=" },
+                    type: "BinaryExpression"
+                }
+            ]
+        },
+        {
+            code: "false <= ((x)) in foo",
+            output: "((x)) >= false in foo",
+            options: ["never"],
+            errors: [
+                {
+                    messageId: "expected",
+                    data: { expectedSide: "right", operator: "<=" },
+                    type: "BinaryExpression"
+                }
+            ]
+        },
+        {
+            code: "x >= 1 instanceof foo",
+            output: "1 <= x instanceof foo",
+            options: ["always"],
+            errors: [
+                {
+                    messageId: "expected",
+                    data: { expectedSide: "left", operator: ">=" },
+                    type: "BinaryExpression"
+                }
+            ]
+        },
+        {
+            code: "x >= 1/**/instanceof foo",
+            output: "1 <= x/**/instanceof foo",
+            options: ["always"],
+            errors: [
+                {
+                    messageId: "expected",
+                    data: { expectedSide: "left", operator: ">=" },
+                    type: "BinaryExpression"
+                }
+            ]
+        },
+        {
+            code: "(x >= 1)instanceof foo",
+            output: "(1 <= x)instanceof foo",
+            options: ["always"],
+            errors: [
+                {
+                    messageId: "expected",
+                    data: { expectedSide: "left", operator: ">=" },
+                    type: "BinaryExpression"
+                }
+            ]
+        },
+        {
+            code: "(x) >= (1)instanceof foo",
+            output: "(1) <= (x)instanceof foo",
+            options: ["always"],
+            errors: [
+                {
+                    messageId: "expected",
+                    data: { expectedSide: "left", operator: ">=" },
+                    type: "BinaryExpression"
+                }
+            ]
+        },
+        {
+            code: "1 > x===foo",
+            output: "x < 1===foo",
+            options: ["never"],
+            errors: [
+                {
+                    messageId: "expected",
+                    data: { expectedSide: "right", operator: ">" },
+                    type: "BinaryExpression"
+                }
+            ]
+        },
+        {
+            code: "1 > x",
+            output: "x < 1",
+            options: ["never"],
+            errors: [
+                {
+                    messageId: "expected",
+                    data: { expectedSide: "right", operator: ">" },
+                    type: "BinaryExpression"
+                }
+            ]
+        },
+
         {
             code: "if (`green` < x.y && x.y < `blue`) {}",
             output: "if (`green` < x.y && `blue` > x.y) {}",
diff --git a/eslint/tests/lib/shared/config-ops.js b/eslint/tests/lib/shared/config-ops.js
deleted file mode 100644 (file)
index 73f3415..0000000
+++ /dev/null
@@ -1,250 +0,0 @@
-/**
- * @fileoverview Tests for ConfigOps
- * @author Nicholas C. Zakas
- */
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const assert = require("chai").assert,
-    leche = require("leche"),
-    util = require("util"),
-    ConfigOps = require("../../../lib/shared/config-ops");
-
-//------------------------------------------------------------------------------
-// Tests
-//------------------------------------------------------------------------------
-
-describe("ConfigOps", () => {
-
-    describe("getRuleSeverity()", () => {
-        const EXPECTED_RESULTS = new Map([
-            [0, 0],
-            [1, 1],
-            [2, 2],
-            [[0], 0],
-            [[1], 1],
-            [[2], 2],
-            ["off", 0],
-            ["warn", 1],
-            ["error", 2],
-            [["off"], 0],
-            [["warn"], 1],
-            [["error"], 2],
-            ["OFF", 0],
-            ["wArN", 1],
-            [["ErRoR"], 2],
-            ["invalid config", 0],
-            [["invalid config"], 0],
-            [3, 0],
-            [[3], 0],
-            [1.5, 0],
-            [[1.5], 0]
-        ]);
-
-        for (const key of EXPECTED_RESULTS.keys()) {
-            it(`returns ${util.inspect(EXPECTED_RESULTS.get(key))} for ${util.inspect(key)}`, () => {
-                assert.strictEqual(ConfigOps.getRuleSeverity(key), EXPECTED_RESULTS.get(key));
-            });
-        }
-    });
-
-    describe("normalizeToStrings()", () => {
-        it("should convert 2 rule setting to error when rule has just a severity", () => {
-            const config = {
-                rules: {
-                    foo: 2,
-                    bar: 2
-                }
-            };
-
-            ConfigOps.normalizeToStrings(config);
-
-            assert.deepStrictEqual(config, {
-                rules: {
-                    foo: "error",
-                    bar: "error"
-                }
-            });
-        });
-
-        it("should convert 2 rule setting to error when rule has array with severity", () => {
-            const config = {
-                rules: {
-                    foo: [2, "something"],
-                    bar: 2
-                }
-            };
-
-            ConfigOps.normalizeToStrings(config);
-
-            assert.deepStrictEqual(config, {
-                rules: {
-                    foo: ["error", "something"],
-                    bar: "error"
-                }
-            });
-        });
-
-        it("should convert 1 rule setting to warn when rule has just a severity", () => {
-            const config = {
-                rules: {
-                    foo: 1,
-                    bar: 1
-                }
-            };
-
-            ConfigOps.normalizeToStrings(config);
-
-            assert.deepStrictEqual(config, {
-                rules: {
-                    foo: "warn",
-                    bar: "warn"
-                }
-            });
-        });
-
-        it("should convert 1 rule setting to warn when rule has array with severity", () => {
-            const config = {
-                rules: {
-                    foo: [1, "something"],
-                    bar: 1
-                }
-            };
-
-            ConfigOps.normalizeToStrings(config);
-
-            assert.deepStrictEqual(config, {
-                rules: {
-                    foo: ["warn", "something"],
-                    bar: "warn"
-                }
-            });
-        });
-
-        it("should convert 0 rule setting to off when rule has just a severity", () => {
-            const config = {
-                rules: {
-                    foo: 0,
-                    bar: 0
-                }
-            };
-
-            ConfigOps.normalizeToStrings(config);
-
-            assert.deepStrictEqual(config, {
-                rules: {
-                    foo: "off",
-                    bar: "off"
-                }
-            });
-        });
-
-        it("should convert 0 rule setting to off when rule has array with severity", () => {
-            const config = {
-                rules: {
-                    foo: [0, "something"],
-                    bar: 0
-                }
-            };
-
-            ConfigOps.normalizeToStrings(config);
-
-            assert.deepStrictEqual(config, {
-                rules: {
-                    foo: ["off", "something"],
-                    bar: "off"
-                }
-            });
-        });
-
-        it("should convert 256 rule setting to off when rule has just a severity", () => {
-            const config = {
-                rules: {
-                    foo: 256,
-                    bar: 256
-                }
-            };
-
-            ConfigOps.normalizeToStrings(config);
-
-            assert.deepStrictEqual(config, {
-                rules: {
-                    foo: "off",
-                    bar: "off"
-                }
-            });
-        });
-
-        it("should convert 256 rule setting to off when rule has array with severity", () => {
-            const config = {
-                rules: {
-                    foo: [256, "something"],
-                    bar: 256
-                }
-            };
-
-            ConfigOps.normalizeToStrings(config);
-
-            assert.deepStrictEqual(config, {
-                rules: {
-                    foo: ["off", "something"],
-                    bar: "off"
-                }
-            });
-        });
-    });
-
-    describe("isError()", () => {
-
-        leche.withData([
-            ["error", true],
-            ["Error", true],
-            [2, true],
-            [["error"], true],
-            [["erRor"], true],
-            [[2], true],
-            [["error", "foo"], true],
-            [["eRror", "bar"], true],
-            [[2, "baz"], true]
-        ], (input, expected) => {
-
-            it(`should return ${expected}when passed ${input}`, () => {
-                const result = ConfigOps.isErrorSeverity(input);
-
-                assert.strictEqual(result, expected);
-            });
-
-        });
-
-    });
-
-    describe("normalizeConfigGlobal", () => {
-        [
-            ["off", "off"],
-            [true, "writable"],
-            ["true", "writable"],
-            [false, "readonly"],
-            ["false", "readonly"],
-            [null, "readonly"],
-            ["writeable", "writable"],
-            ["writable", "writable"],
-            ["readable", "readonly"],
-            ["readonly", "readonly"],
-            ["writable", "writable"]
-        ].forEach(([input, output]) => {
-            it(util.inspect(input), () => {
-                assert.strictEqual(ConfigOps.normalizeConfigGlobal(input), output);
-            });
-        });
-
-        it("throws on other inputs", () => {
-            assert.throws(
-                () => ConfigOps.normalizeConfigGlobal("something else"),
-                /^'something else' is not a valid configuration for a global \(use 'readonly', 'writable', or 'off'\)$/u
-            );
-        });
-    });
-});
index 95976fc9fadd4d4bfab9a807c7eb3a405b0dfc7a..9ba99aea6ba718cf795ebad15b99903e2089d0b8 100644 (file)
@@ -1,3 +1,15 @@
+/*
+ * STOP!!! DO NOT MODIFY.
+ *
+ * This file is part of the ongoing work to move the eslintrc-style config
+ * system into the @eslint/eslintrc package. This file needs to remain
+ * unchanged in order for this work to proceed.
+ *
+ * If you think you need to change this file, please contact @nzakas first.
+ *
+ * Thanks in advance for your cooperation.
+ */
+
 /**
  * @fileoverview Tests for config validator.
  * @author Brandon Mills
diff --git a/eslint/tests/lib/shared/naming.js b/eslint/tests/lib/shared/naming.js
deleted file mode 100644 (file)
index 0a868a8..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-/**
- * @fileoverview Tests for naming util
- */
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const assert = require("chai").assert,
-    leche = require("leche"),
-    naming = require("../../../lib/shared/naming");
-
-//------------------------------------------------------------------------------
-// Tests
-//------------------------------------------------------------------------------
-
-describe("naming", () => {
-    describe("normalizePackageName()", () => {
-
-        leche.withData([
-            ["foo", "eslint-config-foo"],
-            ["eslint-config-foo", "eslint-config-foo"],
-            ["@z/foo", "@z/eslint-config-foo"],
-            ["@z\\foo", "@z/eslint-config-foo"],
-            ["@z\\foo\\bar.js", "@z/eslint-config-foo/bar.js"],
-            ["@z/eslint-config", "@z/eslint-config"],
-            ["@z/eslint-config-foo", "@z/eslint-config-foo"]
-        ], (input, expected) => {
-            it(`should return ${expected} when passed ${input}`, () => {
-                const result = naming.normalizePackageName(input, "eslint-config");
-
-                assert.strictEqual(result, expected);
-            });
-        });
-
-    });
-
-    describe("getShorthandName()", () => {
-
-        leche.withData([
-            ["foo", "foo"],
-            ["eslint-config-foo", "foo"],
-            ["@z", "@z"],
-            ["@z/eslint-config", "@z"],
-            ["@z/foo", "@z/foo"],
-            ["@z/eslint-config-foo", "@z/foo"]
-        ], (input, expected) => {
-            it(`should return ${expected} when passed ${input}`, () => {
-                const result = naming.getShorthandName(input, "eslint-config");
-
-                assert.strictEqual(result, expected);
-            });
-        });
-
-    });
-
-    describe("getNamespaceFromTerm()", () => {
-        it("should remove namespace when passed with namespace", () => {
-            const namespace = naming.getNamespaceFromTerm("@namespace/eslint-plugin-test");
-
-            assert.strictEqual(namespace, "@namespace/");
-        });
-    });
-});
index e89f9a3283d7e77015c501fd5939bdeedc41c53e..c44d4a2e434fe683606b8203b3270b999a462fbd 100644 (file)
@@ -13,7 +13,6 @@ const fs = require("fs"),
     assert = require("chai").assert,
     espree = require("espree"),
     sinon = require("sinon"),
-    leche = require("leche"),
     { Linter } = require("../../../lib/linter"),
     SourceCode = require("../../../lib/source-code/source-code"),
     astUtils = require("../../../lib/shared/ast-utils");
@@ -1791,13 +1790,13 @@ describe("SourceCode", () => {
 
     describe("isSpaceBetween()", () => {
         describe("should return true when there is at least one whitespace character between two tokens", () => {
-            leche.withData([
+            [
                 ["let foo", true],
                 ["let  foo", true],
                 ["let /**/ foo", true],
                 ["let/**/foo", false],
                 ["let/*\n*/foo", false]
-            ], (code, expected) => {
+            ].forEach(([code, expected]) => {
                 describe("when the first given is located before the second", () => {
                     it(code, () => {
                         const ast = espree.parse(code, DEFAULT_CONFIG),
@@ -1829,7 +1828,7 @@ describe("SourceCode", () => {
                 });
             });
 
-            leche.withData([
+            [
                 ["a+b", false],
                 ["a +b", true],
                 ["a/**/+b", false],
@@ -1861,7 +1860,7 @@ describe("SourceCode", () => {
                 ["a/* */+ ` /*\n*/ `/* */+c", true],
                 ["a/* */+` /*\n*/ ` /* */+c", true],
                 ["a/* */+ ` /*\n*/ ` /* */+c", true]
-            ], (code, expected) => {
+            ].forEach(([code, expected]) => {
                 describe("when the first given is located before the second", () => {
                     it(code, () => {
                         const ast = espree.parse(code, DEFAULT_CONFIG),
@@ -1895,7 +1894,7 @@ describe("SourceCode", () => {
         });
 
         describe("should return true when there is at least one whitespace character between a token and a node", () => {
-            leche.withData([
+            [
                 [";let foo = bar", false],
                 [";/**/let foo = bar", false],
                 [";/* */let foo = bar", false],
@@ -1923,7 +1922,7 @@ describe("SourceCode", () => {
                 [";/* */\nlet foo = bar", true],
                 [";\n/**/\nlet foo = bar", true],
                 [";\n/* */\nlet foo = bar", true]
-            ], (code, expected) => {
+            ].forEach(([code, expected]) => {
                 describe("when the first given is located before the second", () => {
                     it(code, () => {
                         const ast = espree.parse(code, DEFAULT_CONFIG),
@@ -1957,7 +1956,7 @@ describe("SourceCode", () => {
         });
 
         describe("should return true when there is at least one whitespace character between a node and a token", () => {
-            leche.withData([
+            [
                 ["let foo = bar;;", false],
                 ["let foo = bar;;;", false],
                 ["let foo = 1; let bar = 2;;", true],
@@ -1985,7 +1984,7 @@ describe("SourceCode", () => {
                 ["let foo = bar;/* */\n;", true],
                 ["let foo = bar;\n/**/\n;", true],
                 ["let foo = bar;\n/* */\n;", true]
-            ], (code, expected) => {
+            ].forEach(([code, expected]) => {
                 describe("when the first given is located before the second", () => {
                     it(code, () => {
                         const ast = espree.parse(code, DEFAULT_CONFIG),
@@ -2019,7 +2018,7 @@ describe("SourceCode", () => {
         });
 
         describe("should return true when there is at least one whitespace character between two nodes", () => {
-            leche.withData([
+            [
                 ["let foo = bar;let baz = qux;", false],
                 ["let foo = bar;/**/let baz = qux;", false],
                 ["let foo = bar;/* */let baz = qux;", false],
@@ -2045,7 +2044,7 @@ describe("SourceCode", () => {
                 ["let foo = bar;\n/**/\nlet baz = qux;", true],
                 ["let foo = bar;\n/* */\nlet baz = qux;", true],
                 ["let foo = 1;let foo2 = 2; let foo3 = 3;", true]
-            ], (code, expected) => {
+            ].forEach(([code, expected]) => {
                 describe("when the first given is located before the second", () => {
                     it(code, () => {
                         const ast = espree.parse(code, DEFAULT_CONFIG),
@@ -2142,9 +2141,9 @@ describe("SourceCode", () => {
         });
 
         describe("should return false either of the arguments' location is inside the other one", () => {
-            leche.withData([
+            [
                 ["let foo = bar;", false]
-            ], (code, expected) => {
+            ].forEach(([code, expected]) => {
                 it(code, () => {
                     const ast = espree.parse(code, DEFAULT_CONFIG),
                         sourceCode = new SourceCode(code, ast);
@@ -2187,13 +2186,13 @@ describe("SourceCode", () => {
 
     describe("isSpaceBetweenTokens()", () => {
         describe("should return true when there is at least one whitespace character between two tokens", () => {
-            leche.withData([
+            [
                 ["let foo", true],
                 ["let  foo", true],
                 ["let /**/ foo", true],
                 ["let/**/foo", false],
                 ["let/*\n*/foo", false]
-            ], (code, expected) => {
+            ].forEach(([code, expected]) => {
                 describe("when the first given is located before the second", () => {
                     it(code, () => {
                         const ast = espree.parse(code, DEFAULT_CONFIG),
@@ -2225,7 +2224,7 @@ describe("SourceCode", () => {
                 });
             });
 
-            leche.withData([
+            [
                 ["a+b", false],
                 ["a +b", true],
                 ["a/**/+b", false],
@@ -2257,7 +2256,7 @@ describe("SourceCode", () => {
                 ["a/* */+ ` /*\n*/ `/* */+c", true],
                 ["a/* */+` /*\n*/ ` /* */+c", true],
                 ["a/* */+ ` /*\n*/ ` /* */+c", true]
-            ], (code, expected) => {
+            ].forEach(([code, expected]) => {
                 describe("when the first given is located before the second", () => {
                     it(code, () => {
                         const ast = espree.parse(code, DEFAULT_CONFIG),
@@ -2291,7 +2290,7 @@ describe("SourceCode", () => {
         });
 
         describe("should return true when there is at least one whitespace character between a token and a node", () => {
-            leche.withData([
+            [
                 [";let foo = bar", false],
                 [";/**/let foo = bar", false],
                 [";/* */let foo = bar", false],
@@ -2319,7 +2318,7 @@ describe("SourceCode", () => {
                 [";/* */\nlet foo = bar", true],
                 [";\n/**/\nlet foo = bar", true],
                 [";\n/* */\nlet foo = bar", true]
-            ], (code, expected) => {
+            ].forEach(([code, expected]) => {
                 describe("when the first given is located before the second", () => {
                     it(code, () => {
                         const ast = espree.parse(code, DEFAULT_CONFIG),
@@ -2353,7 +2352,7 @@ describe("SourceCode", () => {
         });
 
         describe("should return true when there is at least one whitespace character between a node and a token", () => {
-            leche.withData([
+            [
                 ["let foo = bar;;", false],
                 ["let foo = bar;;;", false],
                 ["let foo = 1; let bar = 2;;", true],
@@ -2381,7 +2380,7 @@ describe("SourceCode", () => {
                 ["let foo = bar;/* */\n;", true],
                 ["let foo = bar;\n/**/\n;", true],
                 ["let foo = bar;\n/* */\n;", true]
-            ], (code, expected) => {
+            ].forEach(([code, expected]) => {
                 describe("when the first given is located before the second", () => {
                     it(code, () => {
                         const ast = espree.parse(code, DEFAULT_CONFIG),
@@ -2415,7 +2414,7 @@ describe("SourceCode", () => {
         });
 
         describe("should return true when there is at least one whitespace character between two nodes", () => {
-            leche.withData([
+            [
                 ["let foo = bar;let baz = qux;", false],
                 ["let foo = bar;/**/let baz = qux;", false],
                 ["let foo = bar;/* */let baz = qux;", false],
@@ -2441,7 +2440,7 @@ describe("SourceCode", () => {
                 ["let foo = bar;\n/**/\nlet baz = qux;", true],
                 ["let foo = bar;\n/* */\nlet baz = qux;", true],
                 ["let foo = 1;let foo2 = 2; let foo3 = 3;", true]
-            ], (code, expected) => {
+            ].forEach(([code, expected]) => {
                 describe("when the first given is located before the second", () => {
                     it(code, () => {
                         const ast = espree.parse(code, DEFAULT_CONFIG),
@@ -2538,9 +2537,9 @@ describe("SourceCode", () => {
         });
 
         describe("should return false either of the arguments' location is inside the other one", () => {
-            leche.withData([
+            [
                 ["let foo = bar;", false]
-            ], (code, expected) => {
+            ].forEach(([code, expected]) => {
                 it(code, () => {
                     const ast = espree.parse(code, DEFAULT_CONFIG),
                         sourceCode = new SourceCode(code, ast);
index 3c61a181f394d640c942a831b28fb9b5869ec7bc..4b3d3a05b6717298e3fe09c8824d21daeac10b7c 100644 (file)
@@ -55,7 +55,7 @@ ruleTester.run("consistent-docs-url", rule, {
                 "};"
             ].join("\n"),
             errors: [{
-                message: "Rule is missing a meta.docs property",
+                messageId: "missingMetaDocs",
                 line: 2,
                 column: 5
             }]
@@ -73,7 +73,7 @@ ruleTester.run("consistent-docs-url", rule, {
                 "};"
             ].join("\n"),
             errors: [{
-                message: "Rule is missing a meta.docs.url property",
+                messageId: "missingMetaDocsUrl",
                 line: 3,
                 column: 9
             }]
@@ -92,7 +92,11 @@ ruleTester.run("consistent-docs-url", rule, {
                 "};"
             ].join("\n"),
             errors: [{
-                message: "Incorrect url. Expected \"https://eslint.org/docs/rules/<input>\" but got \"http://example.com/wrong-url\"",
+                messageId: "incorrectUrl",
+                data: {
+                    expected: "https://eslint.org/docs/rules/<input>",
+                    url: "http://example.com/wrong-url"
+                },
                 line: 4,
                 column: 18
             }]
index 512b692fe74ca56444cc4841192fb3046e46ed18..c68f45820507ca8047438d10d3632310396cbaaf 100644 (file)
@@ -52,7 +52,7 @@ function reduceBadExampleSize({
                 comment: true,
                 eslintVisitorKeys: true,
                 eslintScopeManager: true,
-                ecmaVersion: 2018,
+                ecmaVersion: espree.latestEcmaVersion,
                 sourceType: "script"
             })
     },
index 1a52bd5928055cc63e22fab4b4eb230609f2cedb..052fe55f28a8533f65e7f744566ff1254cd9ff68 100644 (file)
@@ -55,7 +55,7 @@ function checkMetaDocsUrl(context, exportsNode) {
     if (!metaDocs) {
         context.report({
             node: metaProperty,
-            message: "Rule is missing a meta.docs property"
+            messageId: "missingMetaDocs"
         });
         return;
     }
@@ -63,7 +63,7 @@ function checkMetaDocsUrl(context, exportsNode) {
     if (!metaDocsUrl) {
         context.report({
             node: metaDocs,
-            message: "Rule is missing a meta.docs.url property"
+            messageId: "missingMetaDocsUrl"
         });
         return;
     }
@@ -75,7 +75,8 @@ function checkMetaDocsUrl(context, exportsNode) {
     if (url !== expected) {
         context.report({
             node: metaDocsUrl.value,
-            message: `Incorrect url. Expected "${expected}" but got "${url}"`
+            messageId: "incorrectUrl",
+            data: { expected, url }
         });
     }
 
@@ -93,7 +94,12 @@ module.exports = {
             recommended: false
         },
         type: "suggestion",
-        schema: []
+        schema: [],
+        messages: {
+            missingMetaDocs: "Rule is missing a meta.docs property.",
+            missingMetaDocsUrl: "Rule is missing a meta.docs.url property.",
+            incorrectUrl: 'Incorrect url. Expected "{{ expected }}" but got "{{ url }}".'
+        }
     },
 
     create(context) {
index f33aa1b66783dd7cb469e6db635bb92313d62325..84700de70d022c0e8c9f3d2d9db1e0ee6c9cbc0c 100644 (file)
@@ -44,6 +44,7 @@
     "guard-for-in": "suggestion",
     "handle-callback-err": "suggestion",
     "id-blacklist": "suggestion",
+    "id-denylist": "suggestion",
     "id-length": "suggestion",
     "id-match": "suggestion",
     "implicit-arrow-linebreak": "layout",
     "no-plusplus": "suggestion",
     "no-process-env": "suggestion",
     "no-process-exit": "suggestion",
+    "no-promise-executor-return": "problem",
     "no-proto": "suggestion",
     "no-prototype-builtins": "problem",
     "no-redeclare": "suggestion",
     "no-unmodified-loop-condition": "problem",
     "no-unneeded-ternary": "suggestion",
     "no-unreachable": "problem",
+    "no-unreachable-loop": "problem",
     "no-unsafe-finally": "problem",
     "no-unsafe-negation": "problem",
     "no-unused-expressions": "suggestion",
     "wrap-regex": "layout",
     "yield-star-spacing": "layout",
     "yoda": "suggestion"
-}
\ No newline at end of file
+}
index 29d60cb4d270682dfbedd627b176e43c3a52be91..2b56290b346380dd18b116874d4e99ef2ef2493f 100644 (file)
@@ -43,7 +43,7 @@ module.exports = {
         ]
     },
     resolve: {
-        mainFields: ["main", "module"]
+        mainFields: ["browser", "main", "module"]
     },
     stats: "errors-only"
 };