From 609c276fc2ca0480a3679cc581306c53c8c8773a Mon Sep 17 00:00:00 2001 From: Thomas Lamprecht Date: Wed, 1 Dec 2021 13:39:06 +0100 Subject: [PATCH] import 8.3.0 source Signed-off-by: Thomas Lamprecht --- eslint/.eslintrc.js | 13 +- eslint/.github/ISSUE_TEMPLATE/BUG_REPORT.md | 78 - eslint/.github/ISSUE_TEMPLATE/CHANGE.md | 35 - eslint/.github/ISSUE_TEMPLATE/NEW_RULE.md | 48 - eslint/.github/ISSUE_TEMPLATE/NEW_SYNTAX.md | 43 + eslint/.github/ISSUE_TEMPLATE/RULE_CHANGE.md | 42 - eslint/.github/ISSUE_TEMPLATE/bug-report.yml | 80 + eslint/.github/ISSUE_TEMPLATE/change.yml | 48 + eslint/.github/ISSUE_TEMPLATE/new-rule.yml | 54 + eslint/.github/ISSUE_TEMPLATE/rule-change.yml | 61 + eslint/.github/PULL_REQUEST_TEMPLATE.md | 8 +- eslint/.github/workflows/ci.yml | 14 +- eslint/.github/workflows/codeql-analysis.yml | 71 + eslint/.github/workflows/stale.yml | 30 + eslint/.markdownlintignore | 1 + eslint/CHANGELOG.md | 364 ++++ eslint/LICENSE | 2 +- eslint/Makefile.js | 117 +- eslint/README.md | 33 +- eslint/bin/eslint.js | 2 +- ...category-list.json => rule-type-list.json} | 11 +- eslint/docs/developer-guide/README.md | 2 +- eslint/docs/developer-guide/architecture.md | 2 +- .../docs/developer-guide/code-conventions.md | 928 +--------- .../developer-guide/code-path-analysis.md | 11 +- .../code-path-analysis/README.md | 550 +----- .../developer-guide/contributing/README.md | 2 +- .../contributing/pull-requests.md | 43 +- .../development-environment.md | 8 +- eslint/docs/developer-guide/nodejs-api.md | 565 +------ .../scope-manager-interface.md | 6 +- eslint/docs/developer-guide/selectors.md | 6 +- eslint/docs/developer-guide/unit-tests.md | 22 +- .../developer-guide/working-with-plugins.md | 8 + .../working-with-rules-deprecated.md | 4 +- .../developer-guide/working-with-rules.md | 50 +- eslint/docs/rules/block-scoped-var.md | 18 + eslint/docs/rules/block-spacing.md | 16 + eslint/docs/rules/brace-style.md | 64 + eslint/docs/rules/class-methods-use-this.md | 53 +- eslint/docs/rules/complexity.md | 47 + eslint/docs/rules/dot-notation.md | 13 + eslint/docs/rules/func-name-matching.md | 38 + eslint/docs/rules/func-names.md | 11 +- eslint/docs/rules/id-denylist.md | 34 + eslint/docs/rules/id-length.md | 6 + eslint/docs/rules/id-match.md | 44 +- eslint/docs/rules/indent-legacy.md | 2 + eslint/docs/rules/indent.md | 52 + eslint/docs/rules/keyword-spacing.md | 15 +- eslint/docs/rules/lines-around-comment.md | 85 +- .../docs/rules/lines-between-class-members.md | 21 + eslint/docs/rules/max-classes-per-file.md | 35 +- eslint/docs/rules/max-depth.md | 44 +- eslint/docs/rules/max-statements.md | 24 + eslint/docs/rules/new-cap.md | 37 +- eslint/docs/rules/no-console.md | 3 +- eslint/docs/rules/no-dupe-class-members.md | 22 +- eslint/docs/rules/no-eq-null.md | 8 + eslint/docs/rules/no-eval.md | 8 + eslint/docs/rules/no-extra-parens.md | 16 + eslint/docs/rules/no-extra-semi.md | 28 +- eslint/docs/rules/no-fallthrough.md | 22 + eslint/docs/rules/no-implicit-globals.md | 2 +- eslint/docs/rules/no-import-assign.md | 4 +- eslint/docs/rules/no-inner-declarations.md | 34 +- eslint/docs/rules/no-invalid-this.md | 20 +- eslint/docs/rules/no-lone-blocks.md | 20 + eslint/docs/rules/no-multi-assign.md | 21 +- eslint/docs/rules/no-new-func.md | 12 +- eslint/docs/rules/no-redeclare.md | 26 +- eslint/docs/rules/no-undef-init.md | 25 +- eslint/docs/rules/no-unreachable.md | 68 +- eslint/docs/rules/no-unused-expressions.md | 54 +- .../rules/no-unused-private-class-members.md | 78 + eslint/docs/rules/no-use-before-define.md | 111 +- eslint/docs/rules/no-useless-computed-key.md | 59 +- eslint/docs/rules/one-var.md | 85 +- eslint/docs/rules/operator-linebreak.md | 70 + eslint/docs/rules/padded-blocks.md | 87 +- .../rules/padding-line-between-statements.md | 16 + eslint/docs/rules/prefer-const.md | 19 +- eslint/docs/rules/prefer-destructuring.md | 11 + .../docs/rules/prefer-named-capture-group.md | 13 +- eslint/docs/rules/require-atomic-updates.md | 129 +- eslint/docs/rules/semi-style.md | 28 + eslint/docs/rules/semi.md | 26 + eslint/docs/rules/space-after-keywords.md | 2 +- eslint/docs/rules/space-before-blocks.md | 5 + eslint/docs/rules/space-before-keywords.md | 2 +- eslint/docs/rules/space-return-throw-case.md | 2 +- eslint/docs/rules/strict.md | 2 + eslint/docs/rules/use-isnan.md | 48 + eslint/docs/rules/vars-on-top.md | 50 +- .../docs/user-guide/command-line-interface.md | 21 +- .../configuring/configuration-files.md | 37 +- .../configuring/language-options.md | 4 +- eslint/docs/user-guide/configuring/rules.md | 2 +- eslint/docs/user-guide/getting-started.md | 2 +- eslint/docs/user-guide/integrations.md | 3 +- eslint/docs/user-guide/migrating-to-8.0.0.md | 297 ++++ eslint/lib/api.js | 24 +- eslint/lib/cli-engine/cli-engine.js | 83 +- eslint/lib/cli-engine/file-enumerator.js | 7 +- eslint/lib/cli-engine/formatters/codeframe.js | 138 -- eslint/lib/cli-engine/formatters/html.js | 2 +- eslint/lib/cli-engine/formatters/table.js | 159 -- eslint/lib/cli-engine/formatters/tap.js | 2 +- eslint/lib/cli-engine/hash.js | 4 +- eslint/lib/cli-engine/xml-escape.js | 2 +- eslint/lib/cli.js | 13 +- eslint/lib/config/default-config.js | 52 + eslint/lib/config/flat-config-array.js | 125 ++ eslint/lib/config/flat-config-schema.js | 452 +++++ eslint/lib/config/rule-validator.js | 186 ++ eslint/lib/eslint/eslint.js | 65 +- eslint/lib/init/autoconfig.js | 41 +- eslint/lib/init/config-file.js | 8 +- eslint/lib/init/config-initializer.js | 29 +- eslint/lib/init/config-rule.js | 31 +- eslint/lib/init/npm-utils.js | 34 +- eslint/lib/init/source-code-utils.js | 11 +- eslint/lib/linter/apply-disable-directives.js | 215 ++- .../code-path-analysis/code-path-analyzer.js | 156 +- .../code-path-analysis/code-path-segment.js | 1 - .../code-path-analysis/code-path-state.js | 5 +- .../linter/code-path-analysis/code-path.js | 19 +- .../code-path-analysis/debug-helpers.js | 4 +- .../linter/code-path-analysis/fork-context.js | 1 - .../linter/code-path-analysis/id-generator.js | 1 - eslint/lib/linter/config-comment-parser.js | 8 +- eslint/lib/linter/linter.js | 133 +- eslint/lib/linter/node-event-generator.js | 12 +- eslint/lib/linter/report-translator.js | 24 +- eslint/lib/linter/rules.js | 3 + eslint/lib/linter/safe-emitter.js | 4 +- eslint/lib/linter/source-code-fixer.js | 4 +- eslint/lib/linter/timing.js | 4 +- eslint/lib/options.js | 10 +- eslint/lib/rule-tester/rule-tester.js | 139 +- eslint/lib/rules/accessor-pairs.js | 1 - eslint/lib/rules/array-bracket-newline.js | 1 - eslint/lib/rules/array-bracket-spacing.js | 1 - eslint/lib/rules/array-callback-return.js | 1 - eslint/lib/rules/array-element-newline.js | 1 - eslint/lib/rules/arrow-body-style.js | 1 - eslint/lib/rules/arrow-parens.js | 1 - eslint/lib/rules/arrow-spacing.js | 1 - eslint/lib/rules/block-scoped-var.js | 3 +- eslint/lib/rules/block-spacing.js | 14 +- eslint/lib/rules/brace-style.js | 7 +- eslint/lib/rules/callback-return.js | 2 +- eslint/lib/rules/camelcase.js | 412 +++-- eslint/lib/rules/capitalized-comments.js | 1 - eslint/lib/rules/class-methods-use-this.js | 75 +- eslint/lib/rules/comma-dangle.js | 4 +- eslint/lib/rules/comma-spacing.js | 1 - eslint/lib/rules/comma-style.js | 6 +- eslint/lib/rules/complexity.js | 111 +- eslint/lib/rules/computed-property-spacing.js | 4 +- eslint/lib/rules/consistent-return.js | 7 +- eslint/lib/rules/consistent-this.js | 3 +- eslint/lib/rules/constructor-super.js | 1 - eslint/lib/rules/curly.js | 14 +- eslint/lib/rules/default-case-last.js | 1 - eslint/lib/rules/default-case.js | 5 +- eslint/lib/rules/default-param-last.js | 3 +- eslint/lib/rules/dot-location.js | 1 - eslint/lib/rules/dot-notation.js | 8 +- eslint/lib/rules/eol-last.js | 8 +- eslint/lib/rules/eqeqeq.js | 3 +- eslint/lib/rules/for-direction.js | 1 - eslint/lib/rules/func-call-spacing.js | 1 - eslint/lib/rules/func-name-matching.js | 15 +- eslint/lib/rules/func-names.js | 2 +- eslint/lib/rules/func-style.js | 1 - .../rules/function-call-argument-newline.js | 1 - eslint/lib/rules/function-paren-newline.js | 2 +- eslint/lib/rules/generator-star-spacing.js | 1 - eslint/lib/rules/getter-return.js | 1 - eslint/lib/rules/global-require.js | 2 +- eslint/lib/rules/grouped-accessor-pairs.js | 1 - eslint/lib/rules/guard-for-in.js | 1 - eslint/lib/rules/handle-callback-err.js | 2 +- eslint/lib/rules/id-blacklist.js | 17 +- eslint/lib/rules/id-denylist.js | 58 +- eslint/lib/rules/id-length.js | 43 +- eslint/lib/rules/id-match.js | 59 +- eslint/lib/rules/implicit-arrow-linebreak.js | 1 - eslint/lib/rules/indent-legacy.js | 2 +- eslint/lib/rules/indent.js | 85 +- eslint/lib/rules/index.js | 3 +- eslint/lib/rules/init-declarations.js | 1 - eslint/lib/rules/jsx-quotes.js | 1 - eslint/lib/rules/key-spacing.js | 36 +- eslint/lib/rules/keyword-spacing.js | 41 +- eslint/lib/rules/line-comment-position.js | 1 - eslint/lib/rules/linebreak-style.js | 1 - eslint/lib/rules/lines-around-comment.js | 62 +- eslint/lib/rules/lines-around-directive.js | 3 +- .../lib/rules/lines-between-class-members.js | 53 +- eslint/lib/rules/max-classes-per-file.js | 39 +- eslint/lib/rules/max-depth.js | 3 +- eslint/lib/rules/max-len.js | 3 +- eslint/lib/rules/max-lines-per-function.js | 3 +- eslint/lib/rules/max-lines.js | 17 +- eslint/lib/rules/max-nested-callbacks.js | 1 - eslint/lib/rules/max-params.js | 1 - eslint/lib/rules/max-statements-per-line.js | 1 - eslint/lib/rules/max-statements.js | 11 +- eslint/lib/rules/multiline-comment-style.js | 1 - eslint/lib/rules/multiline-ternary.js | 1 - eslint/lib/rules/new-cap.js | 4 +- eslint/lib/rules/new-parens.js | 1 - eslint/lib/rules/newline-after-var.js | 9 +- eslint/lib/rules/newline-before-return.js | 3 +- eslint/lib/rules/newline-per-chained-call.js | 3 +- eslint/lib/rules/no-alert.js | 1 - eslint/lib/rules/no-array-constructor.js | 1 - eslint/lib/rules/no-async-promise-executor.js | 1 - eslint/lib/rules/no-await-in-loop.js | 1 - eslint/lib/rules/no-bitwise.js | 11 +- eslint/lib/rules/no-buffer-constructor.js | 2 +- eslint/lib/rules/no-caller.js | 1 - eslint/lib/rules/no-case-declarations.js | 1 - eslint/lib/rules/no-catch-shadow.js | 1 - eslint/lib/rules/no-class-assign.js | 1 - eslint/lib/rules/no-compare-neg-zero.js | 1 - eslint/lib/rules/no-cond-assign.js | 1 - eslint/lib/rules/no-confusing-arrow.js | 1 - eslint/lib/rules/no-console.js | 1 - eslint/lib/rules/no-const-assign.js | 1 - eslint/lib/rules/no-constant-condition.js | 1 - eslint/lib/rules/no-constructor-return.js | 1 - eslint/lib/rules/no-continue.js | 1 - eslint/lib/rules/no-control-regex.js | 1 - eslint/lib/rules/no-debugger.js | 1 - eslint/lib/rules/no-delete-var.js | 1 - eslint/lib/rules/no-div-regex.js | 1 - eslint/lib/rules/no-dupe-args.js | 1 - eslint/lib/rules/no-dupe-class-members.js | 10 +- eslint/lib/rules/no-dupe-else-if.js | 1 - eslint/lib/rules/no-dupe-keys.js | 2 - eslint/lib/rules/no-duplicate-case.js | 1 - eslint/lib/rules/no-duplicate-imports.js | 1 - eslint/lib/rules/no-else-return.js | 1 - eslint/lib/rules/no-empty-character-class.js | 26 +- eslint/lib/rules/no-empty-function.js | 1 - eslint/lib/rules/no-empty-pattern.js | 1 - eslint/lib/rules/no-empty.js | 1 - eslint/lib/rules/no-eq-null.js | 1 - eslint/lib/rules/no-eval.js | 5 +- eslint/lib/rules/no-ex-assign.js | 1 - eslint/lib/rules/no-extend-native.js | 1 - eslint/lib/rules/no-extra-bind.js | 1 - eslint/lib/rules/no-extra-boolean-cast.js | 2 +- eslint/lib/rules/no-extra-label.js | 1 - eslint/lib/rules/no-extra-parens.js | 24 +- eslint/lib/rules/no-extra-semi.js | 3 +- eslint/lib/rules/no-fallthrough.js | 24 +- eslint/lib/rules/no-floating-decimal.js | 1 - eslint/lib/rules/no-func-assign.js | 1 - eslint/lib/rules/no-global-assign.js | 1 - eslint/lib/rules/no-implicit-coercion.js | 1 - eslint/lib/rules/no-implicit-globals.js | 1 - eslint/lib/rules/no-implied-eval.js | 1 - eslint/lib/rules/no-import-assign.js | 1 - eslint/lib/rules/no-inline-comments.js | 1 - eslint/lib/rules/no-inner-declarations.js | 31 +- eslint/lib/rules/no-invalid-regexp.js | 3 +- eslint/lib/rules/no-invalid-this.js | 9 +- eslint/lib/rules/no-irregular-whitespace.js | 1 - eslint/lib/rules/no-iterator.js | 1 - eslint/lib/rules/no-label-var.js | 1 - eslint/lib/rules/no-labels.js | 1 - eslint/lib/rules/no-lone-blocks.js | 11 +- eslint/lib/rules/no-lonely-if.js | 1 - eslint/lib/rules/no-loop-func.js | 3 +- eslint/lib/rules/no-loss-of-precision.js | 3 +- eslint/lib/rules/no-magic-numbers.js | 1 - .../rules/no-misleading-character-class.js | 1 - eslint/lib/rules/no-mixed-operators.js | 5 +- eslint/lib/rules/no-mixed-requires.js | 2 +- eslint/lib/rules/no-mixed-spaces-and-tabs.js | 1 - eslint/lib/rules/no-multi-assign.js | 22 +- eslint/lib/rules/no-multi-spaces.js | 1 - eslint/lib/rules/no-multi-str.js | 1 - eslint/lib/rules/no-multiple-empty-lines.js | 1 - eslint/lib/rules/no-native-reassign.js | 1 - eslint/lib/rules/no-negated-condition.js | 1 - eslint/lib/rules/no-negated-in-lhs.js | 1 - eslint/lib/rules/no-nested-ternary.js | 1 - eslint/lib/rules/no-new-func.js | 41 +- eslint/lib/rules/no-new-object.js | 1 - eslint/lib/rules/no-new-require.js | 2 +- eslint/lib/rules/no-new-symbol.js | 1 - eslint/lib/rules/no-new-wrappers.js | 1 - eslint/lib/rules/no-new.js | 1 - .../lib/rules/no-nonoctal-decimal-escape.js | 8 +- eslint/lib/rules/no-obj-calls.js | 1 - eslint/lib/rules/no-octal-escape.js | 1 - eslint/lib/rules/no-octal.js | 1 - eslint/lib/rules/no-param-reassign.js | 1 - eslint/lib/rules/no-path-concat.js | 2 +- eslint/lib/rules/no-plusplus.js | 1 - eslint/lib/rules/no-process-env.js | 2 +- eslint/lib/rules/no-process-exit.js | 2 +- .../lib/rules/no-promise-executor-return.js | 1 - eslint/lib/rules/no-proto.js | 1 - eslint/lib/rules/no-prototype-builtins.js | 1 - eslint/lib/rules/no-redeclare.js | 3 +- eslint/lib/rules/no-regex-spaces.js | 1 - eslint/lib/rules/no-restricted-exports.js | 1 - eslint/lib/rules/no-restricted-globals.js | 3 +- eslint/lib/rules/no-restricted-imports.js | 11 +- eslint/lib/rules/no-restricted-modules.js | 6 +- eslint/lib/rules/no-restricted-properties.js | 5 +- eslint/lib/rules/no-restricted-syntax.js | 3 +- eslint/lib/rules/no-return-assign.js | 1 - eslint/lib/rules/no-return-await.js | 1 - eslint/lib/rules/no-script-url.js | 4 +- eslint/lib/rules/no-self-assign.js | 1 - eslint/lib/rules/no-self-compare.js | 1 - eslint/lib/rules/no-sequences.js | 1 - eslint/lib/rules/no-setter-return.js | 2 +- .../lib/rules/no-shadow-restricted-names.js | 1 - eslint/lib/rules/no-shadow.js | 3 +- eslint/lib/rules/no-spaced-func.js | 1 - eslint/lib/rules/no-sparse-arrays.js | 1 - eslint/lib/rules/no-sync.js | 4 +- eslint/lib/rules/no-tabs.js | 1 - .../lib/rules/no-template-curly-in-string.js | 1 - eslint/lib/rules/no-ternary.js | 1 - eslint/lib/rules/no-this-before-super.js | 1 - eslint/lib/rules/no-throw-literal.js | 1 - eslint/lib/rules/no-trailing-spaces.js | 1 - eslint/lib/rules/no-undef-init.js | 1 - eslint/lib/rules/no-undef.js | 1 - eslint/lib/rules/no-undefined.js | 1 - eslint/lib/rules/no-underscore-dangle.js | 6 +- eslint/lib/rules/no-unexpected-multiline.js | 1 - .../lib/rules/no-unmodified-loop-condition.js | 1 - eslint/lib/rules/no-unneeded-ternary.js | 1 - eslint/lib/rules/no-unreachable-loop.js | 1 - eslint/lib/rules/no-unreachable.js | 49 +- eslint/lib/rules/no-unsafe-finally.js | 1 - eslint/lib/rules/no-unsafe-negation.js | 6 +- .../lib/rules/no-unsafe-optional-chaining.js | 3 +- eslint/lib/rules/no-unused-expressions.js | 15 +- eslint/lib/rules/no-unused-labels.js | 1 - .../rules/no-unused-private-class-members.js | 194 +++ eslint/lib/rules/no-unused-vars.js | 54 +- eslint/lib/rules/no-use-before-define.js | 250 ++- eslint/lib/rules/no-useless-backreference.js | 3 +- eslint/lib/rules/no-useless-call.js | 1 - eslint/lib/rules/no-useless-catch.js | 1 - eslint/lib/rules/no-useless-computed-key.js | 92 +- eslint/lib/rules/no-useless-concat.js | 1 - eslint/lib/rules/no-useless-constructor.js | 1 - eslint/lib/rules/no-useless-escape.js | 23 +- eslint/lib/rules/no-useless-rename.js | 1 - eslint/lib/rules/no-useless-return.js | 1 - eslint/lib/rules/no-var.js | 1 - eslint/lib/rules/no-void.js | 1 - eslint/lib/rules/no-warning-comments.js | 1 - .../rules/no-whitespace-before-property.js | 1 - eslint/lib/rules/no-with.js | 1 - .../rules/nonblock-statement-body-position.js | 1 - eslint/lib/rules/object-curly-newline.js | 1 - eslint/lib/rules/object-curly-spacing.js | 1 - eslint/lib/rules/object-property-newline.js | 1 - eslint/lib/rules/object-shorthand.js | 11 +- .../lib/rules/one-var-declaration-per-line.js | 1 - eslint/lib/rules/one-var.js | 9 +- eslint/lib/rules/operator-assignment.js | 22 +- eslint/lib/rules/operator-linebreak.js | 30 +- eslint/lib/rules/padded-blocks.js | 10 +- .../rules/padding-line-between-statements.js | 3 +- eslint/lib/rules/prefer-arrow-callback.js | 10 +- eslint/lib/rules/prefer-const.js | 3 +- eslint/lib/rules/prefer-destructuring.js | 9 +- .../rules/prefer-exponentiation-operator.js | 1 - .../lib/rules/prefer-named-capture-group.js | 1 - eslint/lib/rules/prefer-numeric-literals.js | 1 - eslint/lib/rules/prefer-object-spread.js | 1 - .../lib/rules/prefer-promise-reject-errors.js | 1 - eslint/lib/rules/prefer-reflect.js | 1 - eslint/lib/rules/prefer-regex-literals.js | 1 - eslint/lib/rules/prefer-rest-params.js | 1 - eslint/lib/rules/prefer-spread.js | 1 - eslint/lib/rules/prefer-template.js | 1 - eslint/lib/rules/quote-props.js | 17 +- eslint/lib/rules/quotes.js | 2 +- eslint/lib/rules/radix.js | 6 +- eslint/lib/rules/require-atomic-updates.js | 47 +- eslint/lib/rules/require-await.js | 1 - eslint/lib/rules/require-jsdoc.js | 2 +- eslint/lib/rules/require-unicode-regexp.js | 1 - eslint/lib/rules/require-yield.js | 1 - eslint/lib/rules/rest-spread-spacing.js | 1 - eslint/lib/rules/semi-spacing.js | 4 +- eslint/lib/rules/semi-style.js | 27 +- eslint/lib/rules/semi.js | 99 +- eslint/lib/rules/sort-imports.js | 1 - eslint/lib/rules/sort-keys.js | 1 - eslint/lib/rules/sort-vars.js | 1 - eslint/lib/rules/space-before-blocks.js | 17 +- .../lib/rules/space-before-function-paren.js | 1 - eslint/lib/rules/space-in-parens.js | 1 - eslint/lib/rules/space-infix-ops.js | 27 +- eslint/lib/rules/space-unary-ops.js | 1 - eslint/lib/rules/spaced-comment.js | 1 - eslint/lib/rules/strict.js | 1 - eslint/lib/rules/switch-colon-spacing.js | 15 +- eslint/lib/rules/symbol-description.js | 1 - eslint/lib/rules/template-curly-spacing.js | 1 - eslint/lib/rules/template-tag-spacing.js | 1 - eslint/lib/rules/unicode-bom.js | 1 - eslint/lib/rules/use-isnan.js | 6 +- eslint/lib/rules/utils/ast-utils.js | 139 +- .../lib/rules/utils/lazy-loading-rule-map.js | 6 +- eslint/lib/rules/valid-jsdoc.js | 2 +- eslint/lib/rules/valid-typeof.js | 1 - eslint/lib/rules/vars-on-top.js | 40 +- eslint/lib/rules/wrap-iife.js | 1 - eslint/lib/rules/wrap-regex.js | 1 - eslint/lib/rules/yield-star-spacing.js | 1 - eslint/lib/rules/yoda.js | 1 - eslint/lib/shared/ajv.js | 2 +- eslint/lib/shared/config-validator.js | 23 +- eslint/lib/shared/logging.js | 2 +- eslint/lib/shared/relative-module-resolver.js | 10 +- eslint/lib/shared/runtime-info.js | 3 + eslint/lib/shared/traverser.js | 4 +- eslint/lib/shared/types.js | 6 +- eslint/lib/source-code/source-code.js | 20 +- eslint/lib/source-code/token-store/cursor.js | 2 +- eslint/lib/unsupported-api.js | 23 + eslint/package.json | 56 +- eslint/templates/blogpost.md.ejs | 1 - eslint/templates/formatter-examples.md.ejs | 2 +- eslint/tests/_utils/in-memory-fs.js | 496 +----- eslint/tests/_utils/index.js | 12 +- eslint/tests/bin/eslint.js | 34 +- .../class-fields-init--arrow-function.js | 37 + .../class-fields-init--call-expression.js | 26 + .../class-fields-init--conditional.js | 32 + .../class-fields-init--simple.js | 28 + .../class-static-blocks--between-fields.js | 65 + .../class-static-blocks--between-methods.js | 65 + .../class-static-blocks--conditional.js | 29 + .../class-static-blocks--empty.js | 24 + .../class-static-blocks--function-inside.js | 34 + .../class-static-blocks--if-else.js | 29 + .../class-static-blocks--multiple-1.js | 34 + .../class-static-blocks--multiple-2.js | 45 + .../class-static-blocks--simple.js | 24 + .../code-path-analysis/function--new.js | 30 + .../object-literal--conditional.js | 20 + .../exit-on-fatal-error/fatal-error.js | 1 + .../no-fatal-error-rule-violation.js | 3 + .../exit-on-fatal-error/no-fatal-error.js | 1 + .../fixtures/parsers/empty-program-parser.js | 27 + .../fixtures/parsers/enhanced-parser3.js | 3 +- .../prefix-cast-operator-no-space.js | 125 ++ .../prefix-cast-operator-space.js | 125 ++ .../fixtures/rules/make-syntax-error-rule.js | 31 +- .../tests/fixtures/rules/wrong/custom-rule.js | 4 +- .../testers/rule-tester/fixes-one-problem.js | 29 +- .../testers/rule-tester/suggestions.js | 18 +- eslint/tests/lib/api.js | 20 +- eslint/tests/lib/cli-engine/cli-engine.js | 222 ++- .../tests/lib/cli-engine/file-enumerator.js | 23 +- .../lib/cli-engine/formatters/codeframe.js | 483 ------ .../tests/lib/cli-engine/formatters/junit.js | 2 - .../lib/cli-engine/formatters/stylish.js | 4 + .../tests/lib/cli-engine/formatters/table.js | 324 ---- eslint/tests/lib/cli-engine/load-rules.js | 8 + eslint/tests/lib/cli.js | 36 +- eslint/tests/lib/config/flat-config-array.js | 1493 +++++++++++++++++ eslint/tests/lib/eslint/eslint.js | 169 +- eslint/tests/lib/init/config-file.js | 4 +- .../lib/linter/apply-disable-directives.js | 1091 ++++++++++-- .../code-path-analysis/code-path-analyzer.js | 4 +- .../linter/code-path-analysis/code-path.js | 61 +- eslint/tests/lib/linter/interpolate.js | 8 + eslint/tests/lib/linter/linter.js | 857 +++++++++- .../tests/lib/linter/node-event-generator.js | 30 +- eslint/tests/lib/linter/report-translator.js | 33 +- eslint/tests/lib/linter/rule-fixer.js | 44 + eslint/tests/lib/linter/safe-emitter.js | 10 +- eslint/tests/lib/linter/timing.js | 8 + .../tests/lib/rule-tester/no-test-runners.js | 2 +- eslint/tests/lib/rule-tester/rule-tester.js | 590 ++++++- eslint/tests/lib/rules/accessor-pairs.js | 67 + .../tests/lib/rules/array-callback-return.js | 4 + eslint/tests/lib/rules/arrow-parens.js | 1 + eslint/tests/lib/rules/block-scoped-var.js | 15 +- eslint/tests/lib/rules/block-spacing.js | 345 +++- eslint/tests/lib/rules/brace-style.js | 451 ++++- eslint/tests/lib/rules/camelcase.js | 116 +- .../tests/lib/rules/class-methods-use-this.js | 131 +- eslint/tests/lib/rules/comma-style.js | 38 + eslint/tests/lib/rules/complexity.js | 402 ++++- .../lib/rules/computed-property-spacing.js | 134 ++ eslint/tests/lib/rules/consistent-return.js | 112 +- eslint/tests/lib/rules/curly.js | 544 +++++- eslint/tests/lib/rules/default-param-last.js | 8 + eslint/tests/lib/rules/dot-location.js | 28 + eslint/tests/lib/rules/dot-notation.js | 16 +- eslint/tests/lib/rules/eol-last.js | 108 +- eslint/tests/lib/rules/for-direction.js | 2 +- eslint/tests/lib/rules/func-name-matching.js | 324 ++++ eslint/tests/lib/rules/func-names.js | 74 + eslint/tests/lib/rules/getter-return.js | 13 +- eslint/tests/lib/rules/global-require.js | 1 + .../tests/lib/rules/grouped-accessor-pairs.js | 39 +- eslint/tests/lib/rules/id-denylist.js | 50 + eslint/tests/lib/rules/id-length.js | 80 +- eslint/tests/lib/rules/id-match.js | 82 + eslint/tests/lib/rules/indent.js | 998 ++++++++++- eslint/tests/lib/rules/init-declarations.js | 4 + eslint/tests/lib/rules/jsx-quotes.js | 4 + eslint/tests/lib/rules/key-spacing.js | 4 + eslint/tests/lib/rules/keyword-spacing.js | 242 ++- .../tests/lib/rules/lines-around-comment.js | 491 +++++- .../lib/rules/lines-between-class-members.js | 71 +- .../tests/lib/rules/max-classes-per-file.js | 46 + eslint/tests/lib/rules/max-depth.js | 60 +- .../tests/lib/rules/max-lines-per-function.js | 1 + .../tests/lib/rules/max-nested-callbacks.js | 4 + eslint/tests/lib/rules/max-statements.js | 54 +- .../lib/rules/multiline-comment-style.js | 1 - eslint/tests/lib/rules/new-parens.js | 1 + .../lib/rules/newline-per-chained-call.js | 4 + eslint/tests/lib/rules/no-await-in-loop.js | 8 + .../tests/lib/rules/no-buffer-constructor.js | 1 - eslint/tests/lib/rules/no-compare-neg-zero.js | 2 - .../tests/lib/rules/no-dupe-class-members.js | 20 +- .../lib/rules/no-empty-character-class.js | 4 +- eslint/tests/lib/rules/no-eval.js | 16 + .../tests/lib/rules/no-extra-boolean-cast.js | 2 +- eslint/tests/lib/rules/no-extra-parens.js | 49 +- eslint/tests/lib/rules/no-extra-semi.js | 33 + eslint/tests/lib/rules/no-fallthrough.js | 46 + .../tests/lib/rules/no-inner-declarations.js | 70 +- eslint/tests/lib/rules/no-invalid-regexp.js | 7 + eslint/tests/lib/rules/no-invalid-this.js | 84 +- eslint/tests/lib/rules/no-lone-blocks.js | 209 ++- .../tests/lib/rules/no-loss-of-precision.js | 2 +- eslint/tests/lib/rules/no-magic-numbers.js | 2 +- .../rules/no-misleading-character-class.js | 2 +- eslint/tests/lib/rules/no-multi-assign.js | 24 +- .../lib/rules/no-multiple-empty-lines.js | 9 +- eslint/tests/lib/rules/no-new-func.js | 49 +- eslint/tests/lib/rules/no-proto.js | 3 +- .../tests/lib/rules/no-prototype-builtins.js | 2 + eslint/tests/lib/rules/no-redeclare.js | 97 ++ eslint/tests/lib/rules/no-regex-spaces.js | 4 + .../tests/lib/rules/no-restricted-imports.js | 79 + .../tests/lib/rules/no-restricted-modules.js | 81 +- .../lib/rules/no-restricted-properties.js | 16 + eslint/tests/lib/rules/no-self-assign.js | 20 + eslint/tests/lib/rules/no-self-compare.js | 17 +- eslint/tests/lib/rules/no-setter-return.js | 3 +- .../lib/rules/no-shadow-restricted-names.js | 4 + eslint/tests/lib/rules/no-shadow.js | 146 +- .../tests/lib/rules/no-this-before-super.js | 10 +- eslint/tests/lib/rules/no-throw-literal.js | 1 + eslint/tests/lib/rules/no-undef-init.js | 6 +- eslint/tests/lib/rules/no-undef.js | 154 ++ .../tests/lib/rules/no-underscore-dangle.js | 19 +- .../lib/rules/no-unexpected-multiline.js | 56 + .../lib/rules/no-unmodified-loop-condition.js | 4 + eslint/tests/lib/rules/no-unreachable.js | 81 + .../lib/rules/no-unsafe-optional-chaining.js | 5 +- .../tests/lib/rules/no-unused-expressions.js | 23 + .../rules/no-unused-private-class-members.js | 390 +++++ eslint/tests/lib/rules/no-unused-vars.js | 50 + .../tests/lib/rules/no-use-before-define.js | 627 +++++++ eslint/tests/lib/rules/no-useless-call.js | 6 + .../lib/rules/no-useless-computed-key.js | 103 +- eslint/tests/lib/rules/no-useless-concat.js | 1 - eslint/tests/lib/rules/no-useless-escape.js | 4 + eslint/tests/lib/rules/no-useless-rename.js | 4 + eslint/tests/lib/rules/one-var.js | 264 +++ eslint/tests/lib/rules/operator-assignment.js | 3 +- eslint/tests/lib/rules/operator-linebreak.js | 139 ++ eslint/tests/lib/rules/padded-blocks.js | 278 ++- .../rules/padding-line-between-statements.js | 219 +++ eslint/tests/lib/rules/prefer-const.js | 131 +- .../tests/lib/rules/prefer-destructuring.js | 37 +- .../rules/prefer-exponentiation-operator.js | 6 +- .../lib/rules/prefer-numeric-literals.js | 4 + .../tests/lib/rules/prefer-object-spread.js | 13 +- .../lib/rules/prefer-promise-reject-errors.js | 9 +- .../tests/lib/rules/prefer-regex-literals.js | 6 +- eslint/tests/lib/rules/prefer-rest-params.js | 4 + eslint/tests/lib/rules/prefer-spread.js | 13 +- eslint/tests/lib/rules/quote-props.js | 4 + eslint/tests/lib/rules/quotes.js | 90 +- eslint/tests/lib/rules/radix.js | 8 + .../tests/lib/rules/require-atomic-updates.js | 168 +- eslint/tests/lib/rules/require-jsdoc.js | 4 + .../tests/lib/rules/require-unicode-regexp.js | 3 +- eslint/tests/lib/rules/rest-spread-spacing.js | 5 + eslint/tests/lib/rules/semi-spacing.js | 81 +- eslint/tests/lib/rules/semi-style.js | 276 +++ eslint/tests/lib/rules/semi.js | 1358 +++++++++++++-- eslint/tests/lib/rules/space-before-blocks.js | 85 +- eslint/tests/lib/rules/space-infix-ops.js | 45 +- eslint/tests/lib/rules/space-unary-ops.js | 18 + eslint/tests/lib/rules/spaced-comment.js | 8 + eslint/tests/lib/rules/strict.js | 89 +- eslint/tests/lib/rules/symbol-description.js | 4 + eslint/tests/lib/rules/unicode-bom.js | 36 +- eslint/tests/lib/rules/use-isnan.js | 344 ++++ eslint/tests/lib/rules/utils/ast-utils.js | 44 +- eslint/tests/lib/rules/vars-on-top.js | 150 ++ eslint/tests/lib/rules/wrap-regex.js | 1 - eslint/tests/lib/shared/config-validator.js | 9 +- eslint/tests/lib/shared/traverser.js | 8 + eslint/tests/lib/unsupported-api.js | 30 + eslint/tests/tools/code-sample-minimizer.js | 8 + .../internal-rules/consistent-docs-url.js | 105 -- .../consistent-meta-messages.js | 38 - .../internal-rules/multiline-comment-style.js | 9 + .../tools/internal-rules/no-invalid-meta.js | 90 - .../internal-rules/consistent-docs-url.js | 119 -- .../consistent-meta-messages.js | 80 - .../internal-rules/multiline-comment-style.js | 2 +- .../tools/internal-rules/no-invalid-meta.js | 67 +- .../event-generator-tester.js | 2 +- eslint/tools/rule-types.json | 1 + eslint/tools/update-readme.js | 8 +- eslint/tools/update-rule-types.js | 18 - 636 files changed, 23666 insertions(+), 6643 deletions(-) delete mode 100644 eslint/.github/ISSUE_TEMPLATE/BUG_REPORT.md delete mode 100644 eslint/.github/ISSUE_TEMPLATE/CHANGE.md delete mode 100644 eslint/.github/ISSUE_TEMPLATE/NEW_RULE.md create mode 100644 eslint/.github/ISSUE_TEMPLATE/NEW_SYNTAX.md delete mode 100644 eslint/.github/ISSUE_TEMPLATE/RULE_CHANGE.md create mode 100644 eslint/.github/ISSUE_TEMPLATE/bug-report.yml create mode 100644 eslint/.github/ISSUE_TEMPLATE/change.yml create mode 100644 eslint/.github/ISSUE_TEMPLATE/new-rule.yml create mode 100644 eslint/.github/ISSUE_TEMPLATE/rule-change.yml create mode 100644 eslint/.github/workflows/codeql-analysis.yml create mode 100644 eslint/.github/workflows/stale.yml create mode 100644 eslint/.markdownlintignore rename eslint/conf/{category-list.json => rule-type-list.json} (74%) create mode 100644 eslint/docs/rules/no-unused-private-class-members.md create mode 100644 eslint/docs/user-guide/migrating-to-8.0.0.md delete mode 100644 eslint/lib/cli-engine/formatters/codeframe.js delete mode 100644 eslint/lib/cli-engine/formatters/table.js create mode 100644 eslint/lib/config/default-config.js create mode 100644 eslint/lib/config/flat-config-array.js create mode 100644 eslint/lib/config/flat-config-schema.js create mode 100644 eslint/lib/config/rule-validator.js create mode 100644 eslint/lib/rules/no-unused-private-class-members.js create mode 100644 eslint/lib/unsupported-api.js create mode 100644 eslint/tests/fixtures/code-path-analysis/class-fields-init--arrow-function.js create mode 100644 eslint/tests/fixtures/code-path-analysis/class-fields-init--call-expression.js create mode 100644 eslint/tests/fixtures/code-path-analysis/class-fields-init--conditional.js create mode 100644 eslint/tests/fixtures/code-path-analysis/class-fields-init--simple.js create mode 100644 eslint/tests/fixtures/code-path-analysis/class-static-blocks--between-fields.js create mode 100644 eslint/tests/fixtures/code-path-analysis/class-static-blocks--between-methods.js create mode 100644 eslint/tests/fixtures/code-path-analysis/class-static-blocks--conditional.js create mode 100644 eslint/tests/fixtures/code-path-analysis/class-static-blocks--empty.js create mode 100644 eslint/tests/fixtures/code-path-analysis/class-static-blocks--function-inside.js create mode 100644 eslint/tests/fixtures/code-path-analysis/class-static-blocks--if-else.js create mode 100644 eslint/tests/fixtures/code-path-analysis/class-static-blocks--multiple-1.js create mode 100644 eslint/tests/fixtures/code-path-analysis/class-static-blocks--multiple-2.js create mode 100644 eslint/tests/fixtures/code-path-analysis/class-static-blocks--simple.js create mode 100644 eslint/tests/fixtures/code-path-analysis/function--new.js create mode 100644 eslint/tests/fixtures/code-path-analysis/object-literal--conditional.js create mode 100644 eslint/tests/fixtures/exit-on-fatal-error/fatal-error.js create mode 100644 eslint/tests/fixtures/exit-on-fatal-error/no-fatal-error-rule-violation.js create mode 100644 eslint/tests/fixtures/exit-on-fatal-error/no-fatal-error.js create mode 100644 eslint/tests/fixtures/parsers/empty-program-parser.js create mode 100644 eslint/tests/fixtures/parsers/keyword-spacing/prefix-cast-operator-no-space.js create mode 100644 eslint/tests/fixtures/parsers/keyword-spacing/prefix-cast-operator-space.js delete mode 100644 eslint/tests/lib/cli-engine/formatters/codeframe.js delete mode 100644 eslint/tests/lib/cli-engine/formatters/table.js create mode 100644 eslint/tests/lib/config/flat-config-array.js create mode 100644 eslint/tests/lib/rules/no-unused-private-class-members.js create mode 100644 eslint/tests/lib/unsupported-api.js delete mode 100644 eslint/tests/tools/internal-rules/consistent-docs-url.js delete mode 100644 eslint/tests/tools/internal-rules/consistent-meta-messages.js delete mode 100644 eslint/tools/internal-rules/consistent-docs-url.js delete mode 100644 eslint/tools/internal-rules/consistent-meta-messages.js diff --git a/eslint/.eslintrc.js b/eslint/.eslintrc.js index 668bc27..f97d93a 100644 --- a/eslint/.eslintrc.js +++ b/eslint/.eslintrc.js @@ -51,7 +51,7 @@ module.exports = { "plugin:eslint-plugin/recommended" ], parserOptions: { - ecmaVersion: 2020 + ecmaVersion: 2021 }, /* @@ -64,13 +64,12 @@ module.exports = { } }, rules: { - "eslint-plugin/consistent-output": "error", - "eslint-plugin/no-deprecated-context-methods": "error", + "eslint-plugin/prefer-message-ids": "error", "eslint-plugin/prefer-output-null": "error", "eslint-plugin/prefer-placeholders": "error", + "eslint-plugin/prefer-replace-text": "error", "eslint-plugin/report-message-format": ["error", "[^a-z].*\\.$"], "eslint-plugin/require-meta-docs-description": "error", - "eslint-plugin/require-meta-type": "error", "eslint-plugin/test-case-property-ordering": "error", "eslint-plugin/test-case-shorthand-strings": "error", "internal-rules/multiline-comment-style": "error" @@ -80,15 +79,14 @@ module.exports = { files: ["lib/rules/*", "tools/internal-rules/*"], excludedFiles: ["index.js"], rules: { - "internal-rules/no-invalid-meta": "error", - "internal-rules/consistent-meta-messages": "error" + "internal-rules/no-invalid-meta": "error" } }, { files: ["lib/rules/*"], excludedFiles: ["index.js"], rules: { - "internal-rules/consistent-docs-url": "error" + "eslint-plugin/require-meta-docs-url": ["error", { pattern: "https://eslint.org/docs/rules/{{name}}" }] } }, { @@ -105,6 +103,7 @@ module.exports = { // Restrict relative path imports { files: ["lib/*"], + excludedFiles: ["lib/unsupported-api.js"], rules: { "node/no-restricted-require": ["error", [ ...createInternalFilesPatterns() diff --git a/eslint/.github/ISSUE_TEMPLATE/BUG_REPORT.md b/eslint/.github/ISSUE_TEMPLATE/BUG_REPORT.md deleted file mode 100644 index 9b46d29..0000000 --- a/eslint/.github/ISSUE_TEMPLATE/BUG_REPORT.md +++ /dev/null @@ -1,78 +0,0 @@ ---- -name: "\U0001F41E Bug report" -about: Report an issue with ESLint or rules bundled with ESLint -title: '' -labels: bug, repro:needed -assignees: '' - ---- - - - - -**Tell us about your environment** - - - -* **ESLint Version:** -* **Node Version:** -* **npm Version:** -* **Operating System:** - -**What parser (default, `@babel/eslint-parser`, `@typescript-eslint/parser`, etc.) are you using?** - - - -**Please show your full configuration:** - -
-Configuration - - -```js - -``` - -
- -**What did you do? Please include the actual source code causing the issue, as well as the command that you used to run ESLint.** - - -```js - -``` - - -```bash - -``` - -**What did you expect to happen?** - - -**What actually happened? Please copy-paste the actual, raw output from ESLint.** - - -**Steps to reproduce this issue:** - - - -1. -1. -1. - -**Are you willing to submit a pull request to fix this bug?** diff --git a/eslint/.github/ISSUE_TEMPLATE/CHANGE.md b/eslint/.github/ISSUE_TEMPLATE/CHANGE.md deleted file mode 100644 index ef8437a..0000000 --- a/eslint/.github/ISSUE_TEMPLATE/CHANGE.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -name: "\U0001F4DD Non-rule change request" -about: Request a change that is not a bug fix, rule change, or new rule -title: '' -labels: enhancement, triage, core -assignees: '' - ---- - - - - - -**The version of ESLint you are using.** - - -**The problem you want to solve.** - - -**Your take on the correct solution to problem.** - - -**Are you willing to submit a pull request to implement this change?** diff --git a/eslint/.github/ISSUE_TEMPLATE/NEW_RULE.md b/eslint/.github/ISSUE_TEMPLATE/NEW_RULE.md deleted file mode 100644 index 31dfc8c..0000000 --- a/eslint/.github/ISSUE_TEMPLATE/NEW_RULE.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -name: "\U0001F680 New rule proposal" -about: Propose a new rule to be added to ESLint -title: '' -labels: triage, rule, feature -assignees: '' - ---- - - - - -**Please describe what the rule should do:** - - -**What new ECMAScript feature does this rule relate to?** - - - -**What category of rule is this? (place an "X" next to just one item)** - -[ ] Warns about a potential error (problem) -[ ] Suggests an alternate way of doing something (suggestion) -[ ] Other (please specify:) - -**Provide 2-3 code examples that this rule will warn about:** - - -```js - -``` - -**Why should this rule be included in ESLint (instead of a plugin)?** - - -**Are you willing to submit a pull request to implement this rule?** diff --git a/eslint/.github/ISSUE_TEMPLATE/NEW_SYNTAX.md b/eslint/.github/ISSUE_TEMPLATE/NEW_SYNTAX.md new file mode 100644 index 0000000..e03cb6e --- /dev/null +++ b/eslint/.github/ISSUE_TEMPLATE/NEW_SYNTAX.md @@ -0,0 +1,43 @@ +--- +name: "\U0001F4DD Request new syntax support" +about: Request new stage 4 syntax be supported. +title: '' +labels: + - core + - new syntax +assignees: '' + +--- + + + +**What is the name of the syntax to implement?** + + + +**Please provide the TC39 URL for the syntax proposal:** + + + +**Please provide some example code for the new syntax:** + +```js +// example code here +``` + +## Implementation Checklist + +Please check off all items that have already been completed. Be sure to paste the pull request URLs next to each item so we can verify the work as done. + +- [ ] Ecma262 update: +- [ ] ESTree update: +- [ ] Acorn update: +- [ ] `eslint-visitor-keys` update: +- [ ] `espree` update: +- [ ] `eslint-scope` update: +- [ ] `eslint` update: + +**Are you willing to submit a pull request to implement this syntax?** diff --git a/eslint/.github/ISSUE_TEMPLATE/RULE_CHANGE.md b/eslint/.github/ISSUE_TEMPLATE/RULE_CHANGE.md deleted file mode 100644 index ced29fa..0000000 --- a/eslint/.github/ISSUE_TEMPLATE/RULE_CHANGE.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -name: "\U0001F4DD Rule change request" -about: Request a change to an existing rule -title: '' -labels: enhancement, triage, rule -assignees: '' - ---- - - - - -**What rule do you want to change?** - -**Does this change cause the rule to produce more or fewer warnings?** - -**How will the change be implemented? (New option, new default behavior, etc.)?** - -**Please provide some example code that this change will affect:** - - -```js - -``` - -**What does the rule currently do for this code?** - -**What will the rule do after it's changed?** - -**Are you willing to submit a pull request to implement this change?** diff --git a/eslint/.github/ISSUE_TEMPLATE/bug-report.yml b/eslint/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 0000000..8c477f4 --- /dev/null +++ b/eslint/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,80 @@ +name: "\U0001F41E Report a problem" +description: "Report an issue with ESLint or rules bundled with ESLint" +title: "Bug: (fill in)" +labels: + - bug + - "repro:needed" +body: +- type: markdown + attributes: + value: By opening an issue, you agree to abide by the [Open JS Foundation Code of Conduct](https://eslint.org/conduct). +- type: textarea + attributes: + label: Environment + description: | + Please tell us about how you're running ESLint (Run `npx eslint --env-info`.) + value: | + Node version: + npm version: + Local ESLint version: + Global ESLint version: + Operating System: + validations: + required: true +- type: dropdown + attributes: + label: What parser are you using? + description: | + Please keep in mind that some problems are parser-specific. + options: + - "Default (Espree)" + - "@typescript-eslint/parser" + - "@babel/eslint-parser" + - "vue-eslint-parser" + - "@angular-eslint/template-parser" + - Other + validations: + required: true +- type: textarea + attributes: + label: What did you do? + description: | + Please include a *minimal* reproduction case with source code, configuration file, any other information about how you're using ESLint. You can use Markdown in this field. + value: | +
+ Configuration + + ``` + + ``` +
+ + ```js + + ``` + validations: + required: true +- type: textarea + attributes: + label: What did you expect to happen? + description: | + You can use Markdown in this field. + validations: + required: true +- type: textarea + attributes: + label: What actually happened? + description: | + Please copy-paste the actual ESLint output. You can use Markdown in this field. + validations: + required: true +- type: checkboxes + attributes: + label: Participation + options: + - label: I am willing to submit a pull request for this issue. + required: false +- type: textarea + attributes: + label: Additional comments + description: Is there anything else that's important for the team to know? diff --git a/eslint/.github/ISSUE_TEMPLATE/change.yml b/eslint/.github/ISSUE_TEMPLATE/change.yml new file mode 100644 index 0000000..cc2066d --- /dev/null +++ b/eslint/.github/ISSUE_TEMPLATE/change.yml @@ -0,0 +1,48 @@ +name: "\U0001F4DD Request a change (not rule-related)" +description: "Request a change that is not a bug fix, rule change, or new rule" +title: "Change Request: (fill in)" +labels: + - enhancement + - triage + - core +body: +- type: markdown + attributes: + value: By opening an issue, you agree to abide by the [Open JS Foundation Code of Conduct](https://eslint.org/conduct). +- type: input + attributes: + label: ESLint version + description: | + What version of ESLint are you currently using? (Run `npx eslint --version`.) + placeholder: | + e.g. v8.0.0 + validations: + required: true +- type: textarea + attributes: + label: What problem do you want to solve? + description: | + Please explain your use case in as much detail as possible. + placeholder: | + ESLint currently... + validations: + required: true +- type: textarea + attributes: + label: What do you think is the correct solution? + description: | + Please explain how you'd like to change ESLint to address the problem. + placeholder: | + I'd like ESLint to... + validations: + required: true +- type: checkboxes + attributes: + label: Participation + options: + - label: I am willing to submit a pull request for this change. + required: false +- type: textarea + attributes: + label: Additional comments + description: Is there anything else that's important for the team to know? diff --git a/eslint/.github/ISSUE_TEMPLATE/new-rule.yml b/eslint/.github/ISSUE_TEMPLATE/new-rule.yml new file mode 100644 index 0000000..9ef139f --- /dev/null +++ b/eslint/.github/ISSUE_TEMPLATE/new-rule.yml @@ -0,0 +1,54 @@ +name: "\U0001F680 Propose a new core rule" +description: "Propose a new rule to be added to the ESLint core" +title: "New Rule: (fill in)" +labels: + - rule + - feature +body: +- type: markdown + attributes: + value: By opening an issue, you agree to abide by the [Open JS Foundation Code of Conduct](https://eslint.org/conduct). +- type: input + attributes: + label: Rule details + description: What should the new rule do? + validations: + required: true +- type: input + attributes: + label: Related ECMAScript feature + description: What new ECMAScript feature does this rule relate to? Note that we only accept new core rules related to new ECMAScript features. + validations: + required: true +- type: dropdown + attributes: + label: What type of rule is this? + options: + - Warns about a potential problem + - Suggests an alternate way of doing something + - Enforces a formatting/stylistic preference + validations: + required: true +- type: textarea + attributes: + label: Example code + description: Please provide some example JavaScript code that this rule will warn about. This field will render as JavaScript. + render: js + validations: + required: true +- type: textarea + attributes: + label: Why should this rule be in the core instead of a plugin? + description: In general, we prefer that rules be implemented in plugins where they can be tailored to your specific use case. + validations: + required: true +- type: checkboxes + attributes: + label: Participation + options: + - label: I am willing to submit a pull request to implement this rule. + required: false +- type: textarea + attributes: + label: Additional comments + description: Is there anything else that's important for the team to know? diff --git a/eslint/.github/ISSUE_TEMPLATE/rule-change.yml b/eslint/.github/ISSUE_TEMPLATE/rule-change.yml new file mode 100644 index 0000000..86758cb --- /dev/null +++ b/eslint/.github/ISSUE_TEMPLATE/rule-change.yml @@ -0,0 +1,61 @@ +name: "\U0001F4DD Request a rule change" +description: "Request a change to an existing core rule" +title: "Rule Change: (fill in)" +labels: + - enhancement + - rule +body: +- type: markdown + attributes: + value: By opening an issue, you agree to abide by the [Open JS Foundation Code of Conduct](https://eslint.org/conduct). +- type: input + attributes: + label: What rule do you want to change? + validations: + required: true +- type: dropdown + attributes: + label: What change to do you want to make? + options: + - Generate more warnings + - Generate fewer warnings + - Implement autofix + - Implement suggestions + validations: + required: true +- type: dropdown + attributes: + label: How do you think the change should be implemented? + options: + - A new option + - A new default behavior + - Other + validations: + required: true +- type: textarea + attributes: + label: Example code + description: Please provide some example code that this change will affect. This field will render as JavaScript. + render: js + validations: + required: true +- type: textarea + attributes: + label: What does the rule currently do for this code? + validations: + required: true +- type: textarea + attributes: + label: What will the rule do after it's changed? + validations: + required: true +- type: checkboxes + attributes: + label: Participation + options: + - label: I am willing to submit a pull request to implement this change. + required: false +- type: textarea + attributes: + label: Additional comments + description: Is there anything else that's important for the team to know? diff --git a/eslint/.github/PULL_REQUEST_TEMPLATE.md b/eslint/.github/PULL_REQUEST_TEMPLATE.md index f0d6c3e..e15677e 100644 --- a/eslint/.github/PULL_REQUEST_TEMPLATE.md +++ b/eslint/.github/PULL_REQUEST_TEMPLATE.md @@ -6,7 +6,7 @@ #### Prerequisites checklist -- [ ] I have read the [contributing guidelines](https://github.com/eslint/eslint/blob/master/CONTRIBUTING.md). +- [ ] I have read the [contributing guidelines](https://github.com/eslint/eslint/blob/HEAD/CONTRIBUTING.md). #### What is the purpose of this pull request? (put an "X" next to an item) @@ -16,9 +16,9 @@ --> [ ] Documentation update -[ ] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/bug-report.md)) -[ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-proposal.md)) -[ ] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-change-proposal.md)) +[ ] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/HEAD/templates/bug-report.md)) +[ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/HEAD/templates/rule-proposal.md)) +[ ] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/HEAD/templates/rule-change-proposal.md)) [ ] Add autofixing to a rule [ ] Add a CLI option [ ] Add something to the core diff --git a/eslint/.github/workflows/ci.yml b/eslint/.github/workflows/ci.yml index 6ba0bdc..d6baaa9 100644 --- a/eslint/.github/workflows/ci.yml +++ b/eslint/.github/workflows/ci.yml @@ -1,9 +1,9 @@ name: CI on: push: - branches: [master] + branches: [master, main] pull_request: - branches: [master] + branches: [master, main] jobs: verify_files: @@ -11,7 +11,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/setup-node@v2 + with: + node-version: '14.x' - name: Install Packages run: npm install - name: Lint Files @@ -26,7 +28,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node: [16.x, 15.x, 14.x, 13.x, 12.x, 10.x, "10.12.0"] + node: [17.x, 16.x, 14.x, 12.x, "12.22.0"] include: - os: windows-latest node: "12.x" @@ -35,7 +37,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/setup-node@v2 with: node-version: ${{ matrix.node }} - name: Install Packages @@ -50,7 +52,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions/setup-node@v1 + - uses: actions/setup-node@v2 with: node-version: '12' - name: Install Packages diff --git a/eslint/.github/workflows/codeql-analysis.yml b/eslint/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..ef362a4 --- /dev/null +++ b/eslint/.github/workflows/codeql-analysis.yml @@ -0,0 +1,71 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [master, main] + pull_request: + # The branches below must be a subset of the branches above + branches: [master, main] + schedule: + - cron: '28 17 * * 5' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'javascript' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/eslint/.github/workflows/stale.yml b/eslint/.github/workflows/stale.yml new file mode 100644 index 0000000..4574022 --- /dev/null +++ b/eslint/.github/workflows/stale.yml @@ -0,0 +1,30 @@ +# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. +# +# You can adjust the behavior by modifying this file. +# For more information, see: +# https://github.com/actions/stale +name: Mark stale issues and pull requests + +on: + schedule: + - cron: '31 22 * * *' + +jobs: + stale: + + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + + steps: + - uses: actions/stale@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-stale: 60 + days-before-close: 7 + stale-issue-message: 'Oops! It looks like we lost track of this issue. What do we want to do here? This issue will auto-close in 7 days without an update.' + stale-pr-message: 'Oops! It looks like we lost track of this pull request. What do we want to do here? This pull request will auto-close in 7 days without an update.' + exempt-all-assignees: true + exempt-issue-labels: accepted + exempt-pr-labels: accepted diff --git a/eslint/.markdownlintignore b/eslint/.markdownlintignore new file mode 100644 index 0000000..1b763b1 --- /dev/null +++ b/eslint/.markdownlintignore @@ -0,0 +1 @@ +CHANGELOG.md diff --git a/eslint/CHANGELOG.md b/eslint/CHANGELOG.md index 6eaeb9a..abcfbb4 100644 --- a/eslint/CHANGELOG.md +++ b/eslint/CHANGELOG.md @@ -1,3 +1,367 @@ +v8.3.0 - November 21, 2021 + +* [`60b0a29`](https://github.com/eslint/eslint/commit/60b0a292efd1b9cdc318b1e88a0cb7bbf14860b1) feat: add `allowProperties` option to require-atomic-updates (#15238) (Milos Djermanovic) +* [`79278a1`](https://github.com/eslint/eslint/commit/79278a14f1c8747bff8f5cb2100d8776f9d517f2) feat: update no-use-before-define for class static blocks (#15312) (Milos Djermanovic) +* [`8aa7645`](https://github.com/eslint/eslint/commit/8aa764524cf74f0b70d184c7957dbbb5f36a5ac7) fix: update vars-on-top for class static blocks (#15306) (Milos Djermanovic) +* [`479a4cb`](https://github.com/eslint/eslint/commit/479a4cbc70f4032d4accd48e4471629e8635d677) fix: update semi-style for class static blocks (#15309) (Milos Djermanovic) +* [`ddd01dc`](https://github.com/eslint/eslint/commit/ddd01dcd5f14c6ddea5decca46db2f379ec35aeb) feat: update no-redeclare for class static blocks (#15313) (Milos Djermanovic) +* [`de69cec`](https://github.com/eslint/eslint/commit/de69cec834411aeb276a525c11dc10f628df2f51) feat: update no-inner-declarations for class static blocks (#15290) (Milos Djermanovic) +* [`e2fe7ef`](https://github.com/eslint/eslint/commit/e2fe7ef7ea0458de56bed4e4c3d5f71aaebd3f28) feat: support for private-in syntax (fixes #14811) (#15060) (Yosuke Ota) +* [`34bc8d7`](https://github.com/eslint/eslint/commit/34bc8d7cb42d696ec56e0a3c780aa5b042285d6b) feat: Update espree and eslint-scope (#15338) (Brandon Mills) +* [`b171cd7`](https://github.com/eslint/eslint/commit/b171cd7ec839a0481a74a613b0d48a193f16bb6b) feat: update max-depth for class static blocks (#15316) (Milos Djermanovic) +* [`6487df3`](https://github.com/eslint/eslint/commit/6487df371496dd15272e2097e4d2c932532c8727) feat: update padded-blocks for class static blocks (#15333) (Milos Djermanovic) +* [`194f36d`](https://github.com/eslint/eslint/commit/194f36d9c009a72ec72fa9592ea9e31f9f168a52) feat: update the complexity rule for class static blocks (#15328) (Milos Djermanovic) +* [`3530337`](https://github.com/eslint/eslint/commit/3530337e71327d8325d0de01e8e73952010b1a08) feat: update the indent rule for class static blocks (#15324) (Milos Djermanovic) +* [`f03cd14`](https://github.com/eslint/eslint/commit/f03cd146a97ed312d635ac7b53ba0f8d01aa8b47) feat: update lines-around-comment for class static blocks (#15323) (Milos Djermanovic) +* [`5c64747`](https://github.com/eslint/eslint/commit/5c64747a8d7a4f896f0cbce67c7f5e7690837a9b) feat: update brace-style for class static blocks (#15322) (Milos Djermanovic) +* [`df2f1cc`](https://github.com/eslint/eslint/commit/df2f1cc81a559bbc9eee78a3a97315e2927af764) feat: update max-statements for class static blocks (#15315) (Milos Djermanovic) +* [`fd5a0b8`](https://github.com/eslint/eslint/commit/fd5a0b8506e4b6acd740ab966cc2c0e4ff6a4d15) feat: update prefer-const for class static blocks (#15325) (Milos Djermanovic) +* [`b3669fd`](https://github.com/eslint/eslint/commit/b3669fde2316f136af3a16b58b0c44e8ec196cee) feat: code path analysis for class static blocks (#15282) (Milos Djermanovic) +* [`15c1397`](https://github.com/eslint/eslint/commit/15c1397f0063931f50f31af8d110a23c6d660000) feat: update eslint-scope for class static blocks (#15321) (Milos Djermanovic) +* [`1a1bb4b`](https://github.com/eslint/eslint/commit/1a1bb4b1ee87c1b33f2d86ef70b3d81e83377547) feat: update one-var for class static blocks (#15317) (Milos Djermanovic) +* [`9b666e0`](https://github.com/eslint/eslint/commit/9b666e0682bacf44d2a5afa0023874b8b131b5f5) feat: update padding-line-between-statements for class static blocks (#15318) (Milos Djermanovic) +* [`6b85426`](https://github.com/eslint/eslint/commit/6b85426c33ba7ac0206cccef39ccc875b773aeae) docs: Expand `--debug` option description in the CLI documentation (#15308) (darkred) +* [`3ae5258`](https://github.com/eslint/eslint/commit/3ae52584296887e5fc5b0267346294bb920a00e6) docs: the strict rule does not apply to class static blocks (#15314) (Milos Djermanovic) +* [`6d1c666`](https://github.com/eslint/eslint/commit/6d1c666d318cc9e1860e1e2c72fbfa4bdd4a2c4b) fix: update no-invalid-this and no-eval for class static blocks (#15300) (Milos Djermanovic) +* [`d3a267f`](https://github.com/eslint/eslint/commit/d3a267f5f39167e3ee8248ae6b9cae5034d0486f) feat: update class-methods-use-this for class static blocks (#15298) (Milos Djermanovic) +* [`cdaa541`](https://github.com/eslint/eslint/commit/cdaa54130aca7a9c8dfd76c613d0718b048401b2) feat: update no-lone-blocks for class static blocks (#15295) (Milos Djermanovic) +* [`8611538`](https://github.com/eslint/eslint/commit/8611538b47e325c6d6b115bf3d901a26e9ac29f8) feat: update block-spacing for class static blocks (#15297) (Milos Djermanovic) +* [`7b56844`](https://github.com/eslint/eslint/commit/7b56844ece544e501f0173f6427038c9c5e0534f) feat: update keyword-spacing for class static blocks (#15289) (Milos Djermanovic) +* [`ea18711`](https://github.com/eslint/eslint/commit/ea1871146402a77234393613fe56a416382c7f0f) feat: update no-extra-semi for class static blocks (#15287) (Milos Djermanovic) +* [`0f0971f`](https://github.com/eslint/eslint/commit/0f0971ffc2ca6f4513eeffdf5cfa36826c8f4543) feat: update semi rule for class static blocks (#15286) (Milos Djermanovic) +* [`abe740c`](https://github.com/eslint/eslint/commit/abe740ce68dcc9e5413df93b3d80a2e3260f1c18) feat: add examples for block-scoped-var with class static blocks (#15302) (Milos Djermanovic) +* [`9309841`](https://github.com/eslint/eslint/commit/9309841a6cfa85005e0bf79e20415bb9220ba46e) docs: Remove inconsistent colon in pull request docs (#15303) (Jordan Eldredge) +* [`da238cc`](https://github.com/eslint/eslint/commit/da238cc731a9b5ecd48280e0ea4ebd8a48ebeedc) docs: remove deprecation note from lines-around-comment (#15293) (Milos Djermanovic) +* [`1055f16`](https://github.com/eslint/eslint/commit/1055f16fc6f78cc553f0b1462e8af44244c1f84b) docs: no-unused-expressions - class static blocks don't have directives (#15283) (Milos Djermanovic) +* [`edd8d24`](https://github.com/eslint/eslint/commit/edd8d240db8878763dbb147fb6124412c0783a42) chore: upgrade eslint-visitor-keys for class static blocks (#15277) (Milos Djermanovic) +* [`4c55216`](https://github.com/eslint/eslint/commit/4c55216ba958fcc8c3dd29fcaa80298216a48303) docs: Add variables option to no-use-before-define (#15276) (Mathias Rasmussen) +* [`0338fd2`](https://github.com/eslint/eslint/commit/0338fd201614247eeb21e68a26e4b4c8a74f71b0) feat: Normalize ecmaVersion to eslint-scope when using custom parser (#15268) (Yosuke Ota) + +v8.2.0 - November 5, 2021 + +* [`cf5b6be`](https://github.com/eslint/eslint/commit/cf5b6be6f8144f5932cdf062d380f7c0f51e64bd) chore: update @eslint/eslintrc to avoid different versions of `js-yaml` (#15265) (Milos Djermanovic) +* [`c9fefd2`](https://github.com/eslint/eslint/commit/c9fefd2e40348b3e02b855597707a557dc4991d5) feat: report class evaluation TDZ errors in no-use-before-define (#15134) (Milos Djermanovic) +* [`4fd7a6c`](https://github.com/eslint/eslint/commit/4fd7a6ca7339bcbbfa6feda266dcca96684b81c6) perf: don't prepare a fix for valid code in key-spacing (#15239) (Milos Djermanovic) +* [`c415c04`](https://github.com/eslint/eslint/commit/c415c041912a3abbf106cc5713bdcf4ef42590ac) docs: Use string rule severity in CLI examples (#15253) (Kevin Partington) +* [`796587a`](https://github.com/eslint/eslint/commit/796587ad950f6804d60473c2b5998ed3ec71c59e) build: upgrade eslint-release to v3.2.0 to support conventional commits (#15246) (Milos Djermanovic) +* [`12b627d`](https://github.com/eslint/eslint/commit/12b627da401c68a5081822a49068421f1bb2465c) docs: fix typo in `working-with-rules.md` (#15233) (Nitin Kumar) +* [`a86ffc0`](https://github.com/eslint/eslint/commit/a86ffc076014d1de7eefc7456a8ccfb3a2318155) docs: fix broken anchor in configuration files (#15223) (Pierre Berger) +* [`fda533c`](https://github.com/eslint/eslint/commit/fda533cda4b70278acfce4e21b5b1ebe52ff7a3d) chore: update `strip-ansi` dependency (#15221) (Nitin Kumar) +* [`ee8af5f`](https://github.com/eslint/eslint/commit/ee8af5fb864b510ba6b50dcfb706b8b28fdfb74e) docs: Link to unit tests from rule documentation (#15207) (Brandon Mills) +* [`1c0ca3c`](https://github.com/eslint/eslint/commit/1c0ca3c744dd5761d424d19c9cdcccc569dfe34c) docs: add `ci` and `perf` tags for commit (#15215) (Nitin Kumar) +* [`67949bd`](https://github.com/eslint/eslint/commit/67949bd9f3cbda08442d2e5946feb9a4f8b22d85) ci: Remove Node 16 CI prerelease workaround (#14935) (Brandon Mills) + +v8.1.0 - October 22, 2021 + +* [`446b4b3`](https://github.com/eslint/eslint/commit/446b4b3583f90dba7e0ac347b57db013aecc101d) Docs: Update commit message format docs (#15200) (Nicholas C. Zakas) +* [`d9d84a0`](https://github.com/eslint/eslint/commit/d9d84a060362efbaac727f18e3a790098bf0bc4b) Fix: keyword-spacing conflict with space-infix-ops on `>` (fixes #14712) (#15172) (Milos Djermanovic) +* [`a1f7ad7`](https://github.com/eslint/eslint/commit/a1f7ad77e2da00ac7d6daade547fe6bef4ef6003) Fix: allow `baseConfig` to extend preloaded plugin config (fixes #15079) (#15187) (Milos Djermanovic) +* [`3d370fb`](https://github.com/eslint/eslint/commit/3d370fb3596ccd3463c29f1a7a1e3f321dd8083a) New: Add no-unused-private-class-members rule (fixes #14859) (#14895) (Tim van der Lippe) +* [`e926b17`](https://github.com/eslint/eslint/commit/e926b1735c77bf55abc1150b060a535a6c4e2778) New: Add name to RuleTester (#15179) (Gareth Jones) +* [`90a5b6b`](https://github.com/eslint/eslint/commit/90a5b6b4aeff7343783f85418c683f2c9901ab07) Chore: improve performance of `:function` selector (#15181) (Milos Djermanovic) +* [`31af1c8`](https://github.com/eslint/eslint/commit/31af1c8770c7dac9e9686a0549af329abe5a795b) Chore: fix counting of files in performance test (#15190) (Milos Djermanovic) +* [`1b87fa8`](https://github.com/eslint/eslint/commit/1b87fa835892d9da3b945db763196715d8088090) Build: add node v17 (#15193) (唯然) +* [`0fb3bb2`](https://github.com/eslint/eslint/commit/0fb3bb2af3301c92ccd46ece739644a17df89bab) Docs: remove `instanceof` from keyword-spacing docs (#15180) (Milos Djermanovic) +* [`249a040`](https://github.com/eslint/eslint/commit/249a04070f88d2c895af3b78d60d2eff2730730e) Upgrade: `eslint-plugin-eslint-plugin` to v4 (#15169) (Bryan Mishkin) +* [`35f3254`](https://github.com/eslint/eslint/commit/35f3254d5f8027f75a6cb35b58bea10037003be8) Docs: Describe range in rule docs (fixes #14162) (#15174) (Nicholas C. Zakas) +* [`b5049c8`](https://github.com/eslint/eslint/commit/b5049c89a00f1a0da59ecaee74b9b024ef3c3621) Chore: Update stale bot settings (#15173) (Nicholas C. Zakas) +* [`2b32f50`](https://github.com/eslint/eslint/commit/2b32f50460d6858367b25df20b7a717528891e0d) Docs: Fix typo in README.md (#15168) (Dmitriy Fishman) +* [`dd58cd4`](https://github.com/eslint/eslint/commit/dd58cd4afa6ced9016c091fc99a702c97a3e44f0) Chore: migrate master to main (#15062) (Nitesh Seram) +* [`ec0f8e0`](https://github.com/eslint/eslint/commit/ec0f8e0bb7d7ce502ca68fcd13ac323eb6307455) Chore: Add stale issue/PR checker (#15151) (Nicholas C. Zakas) +* [`2cfbd4b`](https://github.com/eslint/eslint/commit/2cfbd4bfd90b31cd728d6595bd1e36667715c84d) Docs: Update README team and sponsors (ESLint Jenkins) + +v8.0.1 - October 13, 2021 + +* [`f9217e5`](https://github.com/eslint/eslint/commit/f9217e527e1c49c6244400c4a58b6d1c14de51db) Upgrade: @eslint/eslintrc@1.0.3 for Jest workaround (#15164) (Brandon Mills) +* [`c584a63`](https://github.com/eslint/eslint/commit/c584a63e2d6d9c0a66e5c5a5d43bc8148c054f5d) Chore: add ecmaVersion 13 to types.js (#15163) (Milos Djermanovic) +* [`ff5fcd4`](https://github.com/eslint/eslint/commit/ff5fcd4d9bf43354a1b85d1f7ec1c4e1c0e5cbd9) Docs: add 13 as allowed ecma version (fixes #15159) (#15162) (唯然) + +v8.0.0 - October 9, 2021 + +* [`7d3f7f0`](https://github.com/eslint/eslint/commit/7d3f7f01281671c4761f8da0d3ae9882a38eca8a) Upgrade: unfrozen @eslint/eslintrc (fixes #15036) (#15146) (Brandon Mills) +* [`2174a6f`](https://github.com/eslint/eslint/commit/2174a6f0e5d18b673604d31e3ca7b790cdc9429b) Fix: require-atomic-updates property assignment message (fixes #15076) (#15109) (Milos Djermanovic) +* [`f885fe0`](https://github.com/eslint/eslint/commit/f885fe06a0a79d91fc72a132fd31edf9ef0502cd) Docs: add note and example for extending the range of fix (refs #13706) (#13748) (Milos Djermanovic) +* [`3da1509`](https://github.com/eslint/eslint/commit/3da1509106f508f0eb8ba48cdfc666225fda7edc) Docs: Add jsdoc `type` annotation to sample rule (#15085) (Bryan Mishkin) +* [`68a49a9`](https://github.com/eslint/eslint/commit/68a49a9446c3286bb9ff24b90713c794b7e1f6f5) Docs: Update Rollup Integrations (#15142) (xiaohai) +* [`d867f81`](https://github.com/eslint/eslint/commit/d867f8100737bb82742debee2b5dc853c5f07c91) Docs: Remove a dot from curly link (#15128) (Mauro Murru) +* [`9f8b919`](https://github.com/eslint/eslint/commit/9f8b91922839b9d438df6cc1d542eea0509ef122) Sponsors: Sync README with website (ESLint Jenkins) +* [`4b08f29`](https://github.com/eslint/eslint/commit/4b08f299a172d3eef09e97e85d19a1612e83ac45) Sponsors: Sync README with website (ESLint Jenkins) +* [`ebc1ba1`](https://github.com/eslint/eslint/commit/ebc1ba1416834b7a52d1e16909ba05c731e97ed4) Sponsors: Sync README with website (ESLint Jenkins) +* [`2d654f1`](https://github.com/eslint/eslint/commit/2d654f115f6e05b59c85434e75cf68204b976f22) Docs: add example .eslintrc.json (#15087) (Nicolas Mattia) +* [`16034f0`](https://github.com/eslint/eslint/commit/16034f09ae6c7a78b8268b4c859928f18de7b9d6) Docs: fix fixable example (#15107) (QiChang Li) +* [`07175b8`](https://github.com/eslint/eslint/commit/07175b8e9532d79e55c499aa27f79f023abda3c3) 8.0.0-rc.0 (ESLint Jenkins) +* [`71faa38`](https://github.com/eslint/eslint/commit/71faa38adada4bd2f1ec0da7e45e6c7c84d1671d) Build: changelog update for 8.0.0-rc.0 (ESLint Jenkins) +* [`67c0074`](https://github.com/eslint/eslint/commit/67c0074fa843fab629f464ff875007a8ee33cc7f) Update: Suggest missing rule in flat config (fixes #14027) (#15074) (Nicholas C. Zakas) +* [`cf34e5c`](https://github.com/eslint/eslint/commit/cf34e5cf5ed5d09eb53c16cca06821c4e34b7b70) Update: space-before-blocks ignore after switch colons (fixes #15082) (#15093) (Milos Djermanovic) +* [`c9efb5f`](https://github.com/eslint/eslint/commit/c9efb5f91937dcb6c8f3d7cb2f59940046d77901) Fix: preserve formatting when rules are removed from disable directives (#15081) (Milos Djermanovic) +* [`14a4739`](https://github.com/eslint/eslint/commit/14a4739ab2233acef995a6dde233de05d067a0f3) Update: `no-new-func` rule catching eval case of `MemberExpression` (#14860) (Mojtaba Samimi) +* [`7f2346b`](https://github.com/eslint/eslint/commit/7f2346b40ffd0d470092e52b995d7ab2648089db) Docs: Update release blog post template (#15094) (Nicholas C. Zakas) +* [`fabdf8a`](https://github.com/eslint/eslint/commit/fabdf8a4e2f82b5fe2f903f015c3e60747a0b143) Chore: Remove `target.all` from `Makefile.js` (#15088) (Hirotaka Tagawa / wafuwafu13) +* [`e3cd141`](https://github.com/eslint/eslint/commit/e3cd1414489ceda460d593ac7e7b14f8ad45d4fc) Sponsors: Sync README with website (ESLint Jenkins) +* [`05d7140`](https://github.com/eslint/eslint/commit/05d7140d46e2b5300d4dc9a60450eed956c95420) Chore: document target global in Makefile.js (#15084) (Hirotaka Tagawa / wafuwafu13) +* [`0a1a850`](https://github.com/eslint/eslint/commit/0a1a850575ca75db017051abe5e931f0f9c8012b) Update: include `ruleId` in error logs (fixes #15037) (#15053) (Ari Perkkiö) +* [`47be800`](https://github.com/eslint/eslint/commit/47be8003d700bc0606495ae42610eaba94e639c5) Chore: test Property > .key with { a = 1 } pattern (fixes #14799) (#15072) (Milos Djermanovic) +* [`a744dfa`](https://github.com/eslint/eslint/commit/a744dfa1f077afe406014f84135f8d26e9a12a94) Docs: Update CLA info (#15058) (Brian Warner) +* [`9fb0f70`](https://github.com/eslint/eslint/commit/9fb0f7040759ea23538997648f2d2d53e7c9db8a) Chore: fix bug report template (#15061) (Milos Djermanovic) +* [`f87e199`](https://github.com/eslint/eslint/commit/f87e199e988f42fc490890eee0642d86c48c85ff) Chore: Cleanup issue templates (#15039) (Nicholas C. Zakas) +* [`660f075`](https://github.com/eslint/eslint/commit/660f075386d0b700faf1a1a94cde9d51899738a3) 8.0.0-beta.2 (ESLint Jenkins) +* [`d148ffd`](https://github.com/eslint/eslint/commit/d148ffdec385e832956c748e36941e598b57b031) Build: changelog update for 8.0.0-beta.2 (ESLint Jenkins) +* [`9e5c2e8`](https://github.com/eslint/eslint/commit/9e5c2e853ace560876c2f2119e134639be8659d0) Upgrade: @eslint/eslintrc@1.0.1 (#15047) (Milos Djermanovic) +* [`7cf96cf`](https://github.com/eslint/eslint/commit/7cf96cf185f849d379b660072d660ec35ac5b46d) Breaking: Disallow reserved words in ES3 (fixes #15017) (#15046) (Milos Djermanovic) +* [`88a3952`](https://github.com/eslint/eslint/commit/88a39520716bdd11f8647e47c57bd8bf91bc7148) Update: support class fields in the `complexity` rule (refs #14857) (#14957) (Milos Djermanovic) +* [`9bd3d87`](https://github.com/eslint/eslint/commit/9bd3d87c8d7369e85f2b7d9b784fed8143191d30) Fix: semicolon-less style in lines-between-class-members (refs #14857) (#15045) (Milos Djermanovic) +* [`6d1ccb6`](https://github.com/eslint/eslint/commit/6d1ccb676fedd1ceb4b1e44abf8133f116a5aecb) Update: enforceForClassFields in class-methods-use-this (refs #14857) (#15018) (YeonJuan) +* [`91e82f5`](https://github.com/eslint/eslint/commit/91e82f5c4cfeab5ac6d01865ce0eb9ea0649df39) Docs: LintMessage.line and column are possibly undefined (#15032) (Brandon Mills) +* [`921ba1e`](https://github.com/eslint/eslint/commit/921ba1ee53e5f2219f09050565b8d69fab517d72) Chore: fix failing cli test (#15041) (Milos Djermanovic) +* [`dd56631`](https://github.com/eslint/eslint/commit/dd5663166a8235512e797522731af1e9651f9392) Docs: remove duplicate code path analysis document (#15033) (Milos Djermanovic) +* [`143a598`](https://github.com/eslint/eslint/commit/143a5987f18f063a47a0646fa1e10e0f88602f6f) Chore: Switch issues to use forms (#15024) (Nicholas C. Zakas) +* [`f966fe6`](https://github.com/eslint/eslint/commit/f966fe6286b6f668812f5155b79d4ee2a8b584b3) Fix: Update semi for class-fields (refs #14857) (#14945) (Nicholas C. Zakas) +* [`8c61f5a`](https://github.com/eslint/eslint/commit/8c61f5ac67682fcfec7fc6faafcf72e4b1a339ff) Docs: add info about non-capturing groups to prefer-named-capture-group (#15009) (Andrzej Wódkiewicz) +* [`dd10937`](https://github.com/eslint/eslint/commit/dd109379f730a988a9e6c0102bcfe443ad0b4b94) Update: added ignoreExpressions option to max-classes-per-file (#15000) (Josh Goldberg) +* [`e9764f3`](https://github.com/eslint/eslint/commit/e9764f3e2fe3f7b6341c9a4381f0dcd23548338e) Fix: no-undef-init should not apply to class fields (refs #14857) (#14994) (Milos Djermanovic) +* [`4338b74`](https://github.com/eslint/eslint/commit/4338b74767fa71e4e8d171f8503aa33d970e509f) Docs: add no-dupe-class-members examples with class fields (refs #14857) (#15005) (Milos Djermanovic) +* [`b4232d4`](https://github.com/eslint/eslint/commit/b4232d47f88611c68a6c0f915b092b68845ecbaf) Chore: Add test that deprecated rules display a deprecated notice (#14989) (TagawaHirotaka) +* [`88b4e3d`](https://github.com/eslint/eslint/commit/88b4e3d191c2577e2e1a283cc5f825feea6271cc) Docs: Make clear how rule options are overridden (fixes #14962) (#14976) (Jake Ob) +* [`4165c7f`](https://github.com/eslint/eslint/commit/4165c7f937f5fc46d4209ae8f763238d73f37238) Docs: Clarify Linter vs ESLint in node.js api docs (fixes #14953) (#14995) (Brian Bartels) +* [`80cfb8f`](https://github.com/eslint/eslint/commit/80cfb8f858888bddfefd7de6b4ecbf5aabe267bc) Docs: fix typo in migration guide (#14985) (Nitin Kumar) +* [`1ddc955`](https://github.com/eslint/eslint/commit/1ddc9559dff437c605e33c156b4380246a231a6e) 8.0.0-beta.1 (ESLint Jenkins) +* [`95cc61e`](https://github.com/eslint/eslint/commit/95cc61e40a89aa2278ae93ae2f35c38737280abb) Build: changelog update for 8.0.0-beta.1 (ESLint Jenkins) +* [`05ca24c`](https://github.com/eslint/eslint/commit/05ca24c57f90f91421b682dca3d7a45b7957fb77) Update: Code path analysis for class fields (fixes #14343) (#14886) (Nicholas C. Zakas) +* [`db15183`](https://github.com/eslint/eslint/commit/db1518374a5e88efedf1ed4609d879f3091af74f) Chore: Refactor comments of tests (#14956) (TagawaHirotaka) +* [`396a0e3`](https://github.com/eslint/eslint/commit/396a0e3c7c82e5d2680d07250008094f336856db) Docs: update ScopeManager with class fields (#14974) (Milos Djermanovic) +* [`6663e7a`](https://github.com/eslint/eslint/commit/6663e7aed498a73108b5e6371f218d9411b87796) Docs: remove `docs` script (fixes #14288) (#14971) (Nitin Kumar) +* [`44c6fc8`](https://github.com/eslint/eslint/commit/44c6fc879de61e9513835d1d4d6ae978d9a43c51) Update: support class fields in func-name-matching (refs #14857) (#14964) (Milos Djermanovic) +* [`44f7de5`](https://github.com/eslint/eslint/commit/44f7de5ee4d934dee540d3d55305126c670f6bfc) Docs: Update deprecated information (#14961) (TagawaHirotaka) +* [`305e14a`](https://github.com/eslint/eslint/commit/305e14af8bd12afc01487abee5c9b0f3eaca989e) Breaking: remove meta.docs.category in core rules (fixes #13398) (#14594) (薛定谔的猫) +* [`a79c9f3`](https://github.com/eslint/eslint/commit/a79c9f35d665c2bcc63267bdf359a8176e0a84ce) Chore: Enforce jsdoc check-line-alignment never (#14955) (Brett Zamir) +* [`a8bcef7`](https://github.com/eslint/eslint/commit/a8bcef70a4a6b1fbb2007075bed754635f27ff01) Docs: Add 2021 and 2022 to supported ECMAScript versions (#14952) (coderaiser) +* [`3409785`](https://github.com/eslint/eslint/commit/3409785a41a5bd2b128ed11b8baf7a59f9e412ee) Fix: camelcase ignoreGlobals shouldn't apply to undef vars (refs #14857) (#14966) (Milos Djermanovic) +* [`b301069`](https://github.com/eslint/eslint/commit/b301069981dc1dcca51df2813dcebdca8c150502) Docs: fix 'When Not To Use' in prefer-named-capture-group (refs #14959) (#14969) (Milos Djermanovic) +* [`2d18db6`](https://github.com/eslint/eslint/commit/2d18db6278320fb97bc8e0bff3518c790566a6a6) Chore: add test for merging `parserOptions` in Linter (#14948) (Milos Djermanovic) +* [`3d7d5fb`](https://github.com/eslint/eslint/commit/3d7d5fb32425e8c04d3eaa0107a2ab03a2e285df) Update: reporting loc for `never` option in `eol-last` (refs #12334) (#14840) (Nitin Kumar) +* [`f110926`](https://github.com/eslint/eslint/commit/f110926a7abcc875a86dd13116f794e4f950e2ba) Update: fix no-unused-vars false negative with comma operator (#14928) (Sachin) +* [`e98f14d`](https://github.com/eslint/eslint/commit/e98f14d356b5ff934dd2a0a1fb226f1b15317ab3) Docs: Fix typo in no-implicit-globals.md (#14954) (jwbth) +* [`9a4ae3b`](https://github.com/eslint/eslint/commit/9a4ae3b68a1afd9483d331997635727fb19a1a99) Chore: Apply comment require-description and check ClassDeclaration (#14949) (Brett Zamir) +* [`8344675`](https://github.com/eslint/eslint/commit/8344675c309a359dd2af5afddba6122f5dc803d0) Chore: fix small typo (#14951) (Sosuke Suzuki) +* [`26b0cd9`](https://github.com/eslint/eslint/commit/26b0cd924e79a0ab2374c0cd813e92055f9fff7b) Update: fix no-unreachable logic for class fields (refs #14857) (#14920) (Milos Djermanovic) +* [`ee1b54f`](https://github.com/eslint/eslint/commit/ee1b54f31fa840e6ec72a313aa4090fdd3e985cd) Fix: keyword-spacing private name compat (refs #14857) (#14946) (Nicholas C. Zakas) +* [`58840ac`](https://github.com/eslint/eslint/commit/58840ac844a61c72eabb603ecfb761812b82a7ed) Chore: Update jsdoc plugin and tweak rules in effect (#14814) (Brett Zamir) +* [`81c60f4`](https://github.com/eslint/eslint/commit/81c60f4a8725738f191580646562d1dca7eee933) Docs: document ESLint api (#14934) (Sam Chen) +* [`c74fe08`](https://github.com/eslint/eslint/commit/c74fe08642c30e1a4cd4e0866251a2d29466add8) Build: Force prerelease peer dep for Node 16 in CI (#14933) (Brandon Mills) +* [`c9947d2`](https://github.com/eslint/eslint/commit/c9947d2a3e0250928d4d80f3b287f10e68fc8db2) 8.0.0-beta.0 (ESLint Jenkins) +* [`027165c`](https://github.com/eslint/eslint/commit/027165cacf62ab1662f4c343ff30b235fd9d46b8) Build: changelog update for 8.0.0-beta.0 (ESLint Jenkins) +* [`be334f9`](https://github.com/eslint/eslint/commit/be334f9d8633e9d193dcb8b36f484547e9d3ab97) Chore: Fix Makefile call to linter.getRules() (#14932) (Brandon Mills) +* [`0c86b68`](https://github.com/eslint/eslint/commit/0c86b68a6e2435eb03b681b51b099b552b521adc) Chore: Replace old syntax for Array flat/flatMap (#14614) (Stephen Wade) +* [`6a89f3f`](https://github.com/eslint/eslint/commit/6a89f3f7b6a3edb3465952521bdf06a220515b95) Chore: ignore `yarn-error.log` and `.pnpm-debug.log` (#14925) (Nitin Kumar) +* [`28fe19c`](https://github.com/eslint/eslint/commit/28fe19c4a9108111932966aa7c9f361c26601d70) Docs: Add v8.0.0 migration guide (fixes #14856) (#14884) (Nicholas C. Zakas) +* [`ec9db63`](https://github.com/eslint/eslint/commit/ec9db63e53a6605a558dcd82947d2425f89887c3) Upgrade: @eslint/eslintrc@1.0.0 (#14865) (Milos Djermanovic) +* [`1f5d088`](https://github.com/eslint/eslint/commit/1f5d0889264c60dddb6fb07a3b1e43f840e84d57) Docs: add an example `Object.assign()` for rule no-import-assign (#14916) (薛定谔的猫) +* [`af96584`](https://github.com/eslint/eslint/commit/af965848c010612c3e136c367cc9b9e2e822f580) Fix: handle computed class fields in operator-linebreak (refs #14857) (#14915) (Milos Djermanovic) +* [`3b6cd89`](https://github.com/eslint/eslint/commit/3b6cd8934b3640ffb6fa49b471babf07f0ad769a) Chore: Add rel/abs path tests in `no-restricted-{imports/modules}` rules (#14910) (Bryan Mishkin) +* [`62c6fe7`](https://github.com/eslint/eslint/commit/62c6fe7d10ff4eeebd196e143f96cfd88818393d) Upgrade: Debug 4.0.1 > 4.3.2 (#14892) (sandesh bafna) +* [`f984515`](https://github.com/eslint/eslint/commit/f98451584a82e41f82ceacd484ea0fe90aa9ce63) Chore: add assertions on reporting location in `semi` (#14899) (Nitin Kumar) +* [`a773b99`](https://github.com/eslint/eslint/commit/a773b99873965652a86bec489193dc42a8923f5f) Fix: no-useless-computed-key edge cases with class fields (refs #14857) (#14903) (Milos Djermanovic) +* [`88db3f5`](https://github.com/eslint/eslint/commit/88db3f54988dddfbda35764ecf1ea16354c4213a) Upgrade: `js-yaml` to v4 (#14890) (Bryan Mishkin) +* [`cbc43da`](https://github.com/eslint/eslint/commit/cbc43daad2ea229fb15a9198efd2bc2721dfb75f) Fix: prefer-destructuring PrivateIdentifier false positive (refs #14857) (#14897) (Milos Djermanovic) +* [`ccb9a91`](https://github.com/eslint/eslint/commit/ccb9a9138acd63457e004630475495954c1be6f4) Fix: dot-notation false positive with private identifier (refs #14857) (#14898) (Milos Djermanovic) +* [`8c35066`](https://github.com/eslint/eslint/commit/8c350660e61284c41a5cc1a5955c858db53c516b) Sponsors: Sync README with website (ESLint Jenkins) +* [`a3dd825`](https://github.com/eslint/eslint/commit/a3dd8257252f392de5cf793c36ecab2acd955659) Sponsors: Sync README with website (ESLint Jenkins) +* [`c4e5802`](https://github.com/eslint/eslint/commit/c4e58023f22381508babfc52087853b5e3965b9c) Docs: improve rule details for `no-console` (fixes #14793) (#14901) (Nitin Kumar) +* [`9052eee`](https://github.com/eslint/eslint/commit/9052eee07a459dc059cd92f657a3ae73acc95bb5) Update: check class fields in no-extra-parens (refs #14857) (#14906) (Milos Djermanovic) +* [`5c3a470`](https://github.com/eslint/eslint/commit/5c3a47072aeb5cfda40a1eb20b43a10c5ca7aab3) Docs: add class fields in no-multi-assign documentation (refs #14857) (#14907) (Milos Djermanovic) +* [`d234d89`](https://github.com/eslint/eslint/commit/d234d890b383837f8e4bda0f6ce1e2a348f9835e) Docs: add class fields in func-names documentation (refs #14857) (#14908) (Milos Djermanovic) +* [`ae6072b`](https://github.com/eslint/eslint/commit/ae6072b1de5c8b30ce6c58290852082439c40b30) Upgrade: `eslint-visitor-keys` to v3 (#14902) (Bryan Mishkin) +* [`e53d8cf`](https://github.com/eslint/eslint/commit/e53d8cf9d73bd105cf6ba4f6b5477ccc4b980939) Upgrade: `markdownlint` dev dependencies (#14883) (Bryan Mishkin) +* [`d66e941`](https://github.com/eslint/eslint/commit/d66e9414be60e05badb96bc3e1a55ca34636d7f8) Upgrade: @humanwhocodes/config-array to 0.6 (#14891) (Bryan Mishkin) +* [`149230c`](https://github.com/eslint/eslint/commit/149230ce7e296c029a0b6c085216fc0360ed4c65) Chore: Specify Node 14.x for Verify Files CI job (#14896) (Milos Djermanovic) +* [`537cf6a`](https://github.com/eslint/eslint/commit/537cf6a0e78ee9b7167e7f8c56f4053d3fb5b2d7) Chore: update `glob-parent` (fixes #14879)(#14887) (Nitin Kumar) +* [`f7b4a3f`](https://github.com/eslint/eslint/commit/f7b4a3f6a44e167c71985d373f73eebd3a4d9556) Chore: update dev deps to latest (#14624) (薛定谔的猫) +* [`24c9f2a`](https://github.com/eslint/eslint/commit/24c9f2ac57efcd699ca69695c82e51ce5742df7b) Breaking: Strict package exports (refs #13654) (#14706) (Nicholas C. Zakas) +* [`86d31a4`](https://github.com/eslint/eslint/commit/86d31a4951e3a39e359e284f5fe336ac477369fe) Breaking: disallow SourceCode#getComments() in RuleTester (refs #14744) (#14769) (Milos Djermanovic) +* [`1d2213d`](https://github.com/eslint/eslint/commit/1d2213deb69c5901c1950bbe648aa819e7e742ed) Breaking: Fixable disable directives (fixes #11815) (#14617) (Josh Goldberg) +* [`4a7aab7`](https://github.com/eslint/eslint/commit/4a7aab7d4323ff7027eebca709d4e95a9aaa80bc) Breaking: require `meta` for fixable rules (fixes #13349) (#14634) (Milos Djermanovic) +* [`d6a761f`](https://github.com/eslint/eslint/commit/d6a761f9b6582e9f71705161be827ca303ef183f) Breaking: Require `meta.hasSuggestions` for rules with suggestions (#14573) (Bryan Mishkin) +* [`6bd747b`](https://github.com/eslint/eslint/commit/6bd747b5b7731195224875b952a9ea61445a9938) Breaking: support new regex d flag (fixes #14640) (#14653) (Yosuke Ota) +* [`8b4f3ab`](https://github.com/eslint/eslint/commit/8b4f3abdb794feb3be31959bb44bfb0ef6318e8e) Breaking: fix comma-dangle schema (fixes #13739) (#14030) (Joakim Nilsson) +* [`b953a4e`](https://github.com/eslint/eslint/commit/b953a4ee12f120658a9ec27d1f8ca88dd3dfb599) Breaking: upgrade espree and support new class features (refs #14343) (#14591) (Toru Nagashima) +* [`8cce06c`](https://github.com/eslint/eslint/commit/8cce06cb39886902ce0d2e6882f46c3bf52fb955) Breaking: add some rules to eslint:recommended (refs #14673) (#14691) (薛定谔的猫) +* [`86bb63b`](https://github.com/eslint/eslint/commit/86bb63b370e0ff350e988a5fa228a8234abe800c) Breaking: Drop `codeframe` and `table` formatters (#14316) (Federico Brigante) +* [`f3cb320`](https://github.com/eslint/eslint/commit/f3cb3208c8952a6218d54658cfda85942b9fda42) Breaking: drop node v10/v13/v15 (fixes #14023) (#14592) (薛定谔的猫) +* [`b8b2d55`](https://github.com/eslint/eslint/commit/b8b2d5553b0de23e8b72ee45949650cd5f9a10d2) Build: add codeql (#14729) (薛定谔的猫) +* [`e037d61`](https://github.com/eslint/eslint/commit/e037d61a12ad17a36e05dcf65aa63fad303c79b9) Docs: Mention workaround for escaping the slash character in selectors (#14675) (Aria) +* [`81f03b6`](https://github.com/eslint/eslint/commit/81f03b6ad69c7f67ad6ba72e02e73266aa8f7696) Docs: Update license copyright (#14877) (Nicholas C. Zakas) +* [`fa1c07c`](https://github.com/eslint/eslint/commit/fa1c07c0d65ce21a30f5bb4a9f2ac511f8df6446) Sponsors: Sync README with website (ESLint Jenkins) +* [`e31f492`](https://github.com/eslint/eslint/commit/e31f49206f94e2b3977ec37892d4b87ab1e46872) Sponsors: Sync README with website (ESLint Jenkins) +* [`8307256`](https://github.com/eslint/eslint/commit/83072561b006a558d026c5a507f92945b821a0cd) Sponsors: Sync README with website (ESLint Jenkins) + +v8.0.0-rc.0 - September 24, 2021 + +* [`67c0074`](https://github.com/eslint/eslint/commit/67c0074fa843fab629f464ff875007a8ee33cc7f) Update: Suggest missing rule in flat config (fixes #14027) (#15074) (Nicholas C. Zakas) +* [`cf34e5c`](https://github.com/eslint/eslint/commit/cf34e5cf5ed5d09eb53c16cca06821c4e34b7b70) Update: space-before-blocks ignore after switch colons (fixes #15082) (#15093) (Milos Djermanovic) +* [`c9efb5f`](https://github.com/eslint/eslint/commit/c9efb5f91937dcb6c8f3d7cb2f59940046d77901) Fix: preserve formatting when rules are removed from disable directives (#15081) (Milos Djermanovic) +* [`14a4739`](https://github.com/eslint/eslint/commit/14a4739ab2233acef995a6dde233de05d067a0f3) Update: `no-new-func` rule catching eval case of `MemberExpression` (#14860) (Mojtaba Samimi) +* [`7f2346b`](https://github.com/eslint/eslint/commit/7f2346b40ffd0d470092e52b995d7ab2648089db) Docs: Update release blog post template (#15094) (Nicholas C. Zakas) +* [`fabdf8a`](https://github.com/eslint/eslint/commit/fabdf8a4e2f82b5fe2f903f015c3e60747a0b143) Chore: Remove `target.all` from `Makefile.js` (#15088) (Hirotaka Tagawa / wafuwafu13) +* [`e3cd141`](https://github.com/eslint/eslint/commit/e3cd1414489ceda460d593ac7e7b14f8ad45d4fc) Sponsors: Sync README with website (ESLint Jenkins) +* [`05d7140`](https://github.com/eslint/eslint/commit/05d7140d46e2b5300d4dc9a60450eed956c95420) Chore: document target global in Makefile.js (#15084) (Hirotaka Tagawa / wafuwafu13) +* [`0a1a850`](https://github.com/eslint/eslint/commit/0a1a850575ca75db017051abe5e931f0f9c8012b) Update: include `ruleId` in error logs (fixes #15037) (#15053) (Ari Perkkiö) +* [`47be800`](https://github.com/eslint/eslint/commit/47be8003d700bc0606495ae42610eaba94e639c5) Chore: test Property > .key with { a = 1 } pattern (fixes #14799) (#15072) (Milos Djermanovic) +* [`a744dfa`](https://github.com/eslint/eslint/commit/a744dfa1f077afe406014f84135f8d26e9a12a94) Docs: Update CLA info (#15058) (Brian Warner) +* [`9fb0f70`](https://github.com/eslint/eslint/commit/9fb0f7040759ea23538997648f2d2d53e7c9db8a) Chore: fix bug report template (#15061) (Milos Djermanovic) +* [`f87e199`](https://github.com/eslint/eslint/commit/f87e199e988f42fc490890eee0642d86c48c85ff) Chore: Cleanup issue templates (#15039) (Nicholas C. Zakas) + +v8.0.0-beta.2 - September 10, 2021 + +* [`9e5c2e8`](https://github.com/eslint/eslint/commit/9e5c2e853ace560876c2f2119e134639be8659d0) Upgrade: @eslint/eslintrc@1.0.1 (#15047) (Milos Djermanovic) +* [`7cf96cf`](https://github.com/eslint/eslint/commit/7cf96cf185f849d379b660072d660ec35ac5b46d) Breaking: Disallow reserved words in ES3 (fixes #15017) (#15046) (Milos Djermanovic) +* [`88a3952`](https://github.com/eslint/eslint/commit/88a39520716bdd11f8647e47c57bd8bf91bc7148) Update: support class fields in the `complexity` rule (refs #14857) (#14957) (Milos Djermanovic) +* [`9bd3d87`](https://github.com/eslint/eslint/commit/9bd3d87c8d7369e85f2b7d9b784fed8143191d30) Fix: semicolon-less style in lines-between-class-members (refs #14857) (#15045) (Milos Djermanovic) +* [`6d1ccb6`](https://github.com/eslint/eslint/commit/6d1ccb676fedd1ceb4b1e44abf8133f116a5aecb) Update: enforceForClassFields in class-methods-use-this (refs #14857) (#15018) (YeonJuan) +* [`91e82f5`](https://github.com/eslint/eslint/commit/91e82f5c4cfeab5ac6d01865ce0eb9ea0649df39) Docs: LintMessage.line and column are possibly undefined (#15032) (Brandon Mills) +* [`921ba1e`](https://github.com/eslint/eslint/commit/921ba1ee53e5f2219f09050565b8d69fab517d72) Chore: fix failing cli test (#15041) (Milos Djermanovic) +* [`dd56631`](https://github.com/eslint/eslint/commit/dd5663166a8235512e797522731af1e9651f9392) Docs: remove duplicate code path analysis document (#15033) (Milos Djermanovic) +* [`143a598`](https://github.com/eslint/eslint/commit/143a5987f18f063a47a0646fa1e10e0f88602f6f) Chore: Switch issues to use forms (#15024) (Nicholas C. Zakas) +* [`f966fe6`](https://github.com/eslint/eslint/commit/f966fe6286b6f668812f5155b79d4ee2a8b584b3) Fix: Update semi for class-fields (refs #14857) (#14945) (Nicholas C. Zakas) +* [`8c61f5a`](https://github.com/eslint/eslint/commit/8c61f5ac67682fcfec7fc6faafcf72e4b1a339ff) Docs: add info about non-capturing groups to prefer-named-capture-group (#15009) (Andrzej Wódkiewicz) +* [`dd10937`](https://github.com/eslint/eslint/commit/dd109379f730a988a9e6c0102bcfe443ad0b4b94) Update: added ignoreExpressions option to max-classes-per-file (#15000) (Josh Goldberg) +* [`e9764f3`](https://github.com/eslint/eslint/commit/e9764f3e2fe3f7b6341c9a4381f0dcd23548338e) Fix: no-undef-init should not apply to class fields (refs #14857) (#14994) (Milos Djermanovic) +* [`4338b74`](https://github.com/eslint/eslint/commit/4338b74767fa71e4e8d171f8503aa33d970e509f) Docs: add no-dupe-class-members examples with class fields (refs #14857) (#15005) (Milos Djermanovic) +* [`b4232d4`](https://github.com/eslint/eslint/commit/b4232d47f88611c68a6c0f915b092b68845ecbaf) Chore: Add test that deprecated rules display a deprecated notice (#14989) (TagawaHirotaka) +* [`88b4e3d`](https://github.com/eslint/eslint/commit/88b4e3d191c2577e2e1a283cc5f825feea6271cc) Docs: Make clear how rule options are overridden (fixes #14962) (#14976) (Jake Ob) +* [`4165c7f`](https://github.com/eslint/eslint/commit/4165c7f937f5fc46d4209ae8f763238d73f37238) Docs: Clarify Linter vs ESLint in node.js api docs (fixes #14953) (#14995) (Brian Bartels) +* [`80cfb8f`](https://github.com/eslint/eslint/commit/80cfb8f858888bddfefd7de6b4ecbf5aabe267bc) Docs: fix typo in migration guide (#14985) (Nitin Kumar) + +v8.0.0-beta.1 - August 27, 2021 + +* [`41617ec`](https://github.com/eslint/eslint/commit/41617ec3c4bc8bd1ba5f66521185be1566e6f5f4) Revert "allow all directives in line comments" (fixes #14960) (#14973) (薛定谔的猫) +* [`05ca24c`](https://github.com/eslint/eslint/commit/05ca24c57f90f91421b682dca3d7a45b7957fb77) Update: Code path analysis for class fields (fixes #14343) (#14886) (Nicholas C. Zakas) +* [`db15183`](https://github.com/eslint/eslint/commit/db1518374a5e88efedf1ed4609d879f3091af74f) Chore: Refactor comments of tests (#14956) (TagawaHirotaka) +* [`396a0e3`](https://github.com/eslint/eslint/commit/396a0e3c7c82e5d2680d07250008094f336856db) Docs: update ScopeManager with class fields (#14974) (Milos Djermanovic) +* [`6663e7a`](https://github.com/eslint/eslint/commit/6663e7aed498a73108b5e6371f218d9411b87796) Docs: remove `docs` script (fixes #14288) (#14971) (Nitin Kumar) +* [`44c6fc8`](https://github.com/eslint/eslint/commit/44c6fc879de61e9513835d1d4d6ae978d9a43c51) Update: support class fields in func-name-matching (refs #14857) (#14964) (Milos Djermanovic) +* [`44f7de5`](https://github.com/eslint/eslint/commit/44f7de5ee4d934dee540d3d55305126c670f6bfc) Docs: Update deprecated information (#14961) (TagawaHirotaka) +* [`305e14a`](https://github.com/eslint/eslint/commit/305e14af8bd12afc01487abee5c9b0f3eaca989e) Breaking: remove meta.docs.category in core rules (fixes #13398) (#14594) (薛定谔的猫) +* [`a79c9f3`](https://github.com/eslint/eslint/commit/a79c9f35d665c2bcc63267bdf359a8176e0a84ce) Chore: Enforce jsdoc check-line-alignment never (#14955) (Brett Zamir) +* [`a8bcef7`](https://github.com/eslint/eslint/commit/a8bcef70a4a6b1fbb2007075bed754635f27ff01) Docs: Add 2021 and 2022 to supported ECMAScript versions (#14952) (coderaiser) +* [`3409785`](https://github.com/eslint/eslint/commit/3409785a41a5bd2b128ed11b8baf7a59f9e412ee) Fix: camelcase ignoreGlobals shouldn't apply to undef vars (refs #14857) (#14966) (Milos Djermanovic) +* [`b301069`](https://github.com/eslint/eslint/commit/b301069981dc1dcca51df2813dcebdca8c150502) Docs: fix 'When Not To Use' in prefer-named-capture-group (refs #14959) (#14969) (Milos Djermanovic) +* [`2d18db6`](https://github.com/eslint/eslint/commit/2d18db6278320fb97bc8e0bff3518c790566a6a6) Chore: add test for merging `parserOptions` in Linter (#14948) (Milos Djermanovic) +* [`3d7d5fb`](https://github.com/eslint/eslint/commit/3d7d5fb32425e8c04d3eaa0107a2ab03a2e285df) Update: reporting loc for `never` option in `eol-last` (refs #12334) (#14840) (Nitin Kumar) +* [`f110926`](https://github.com/eslint/eslint/commit/f110926a7abcc875a86dd13116f794e4f950e2ba) Update: fix no-unused-vars false negative with comma operator (#14928) (Sachin) +* [`e98f14d`](https://github.com/eslint/eslint/commit/e98f14d356b5ff934dd2a0a1fb226f1b15317ab3) Docs: Fix typo in no-implicit-globals.md (#14954) (jwbth) +* [`9a4ae3b`](https://github.com/eslint/eslint/commit/9a4ae3b68a1afd9483d331997635727fb19a1a99) Chore: Apply comment require-description and check ClassDeclaration (#14949) (Brett Zamir) +* [`8344675`](https://github.com/eslint/eslint/commit/8344675c309a359dd2af5afddba6122f5dc803d0) Chore: fix small typo (#14951) (Sosuke Suzuki) +* [`26b0cd9`](https://github.com/eslint/eslint/commit/26b0cd924e79a0ab2374c0cd813e92055f9fff7b) Update: fix no-unreachable logic for class fields (refs #14857) (#14920) (Milos Djermanovic) +* [`ee1b54f`](https://github.com/eslint/eslint/commit/ee1b54f31fa840e6ec72a313aa4090fdd3e985cd) Fix: keyword-spacing private name compat (refs #14857) (#14946) (Nicholas C. Zakas) +* [`58840ac`](https://github.com/eslint/eslint/commit/58840ac844a61c72eabb603ecfb761812b82a7ed) Chore: Update jsdoc plugin and tweak rules in effect (#14814) (Brett Zamir) +* [`81c60f4`](https://github.com/eslint/eslint/commit/81c60f4a8725738f191580646562d1dca7eee933) Docs: document ESLint api (#14934) (Sam Chen) +* [`c74fe08`](https://github.com/eslint/eslint/commit/c74fe08642c30e1a4cd4e0866251a2d29466add8) Build: Force prerelease peer dep for Node 16 in CI (#14933) (Brandon Mills) + +v8.0.0-beta.0 - August 14, 2021 + +* [`be334f9`](https://github.com/eslint/eslint/commit/be334f9d8633e9d193dcb8b36f484547e9d3ab97) Chore: Fix Makefile call to linter.getRules() (#14932) (Brandon Mills) +* [`0c86b68`](https://github.com/eslint/eslint/commit/0c86b68a6e2435eb03b681b51b099b552b521adc) Chore: Replace old syntax for Array flat/flatMap (#14614) (Stephen Wade) +* [`6a89f3f`](https://github.com/eslint/eslint/commit/6a89f3f7b6a3edb3465952521bdf06a220515b95) Chore: ignore `yarn-error.log` and `.pnpm-debug.log` (#14925) (Nitin Kumar) +* [`28fe19c`](https://github.com/eslint/eslint/commit/28fe19c4a9108111932966aa7c9f361c26601d70) Docs: Add v8.0.0 migration guide (fixes #14856) (#14884) (Nicholas C. Zakas) +* [`ec9db63`](https://github.com/eslint/eslint/commit/ec9db63e53a6605a558dcd82947d2425f89887c3) Upgrade: @eslint/eslintrc@1.0.0 (#14865) (Milos Djermanovic) +* [`1f5d088`](https://github.com/eslint/eslint/commit/1f5d0889264c60dddb6fb07a3b1e43f840e84d57) Docs: add an example `Object.assign()` for rule no-import-assign (#14916) (薛定谔的猫) +* [`af96584`](https://github.com/eslint/eslint/commit/af965848c010612c3e136c367cc9b9e2e822f580) Fix: handle computed class fields in operator-linebreak (refs #14857) (#14915) (Milos Djermanovic) +* [`3b6cd89`](https://github.com/eslint/eslint/commit/3b6cd8934b3640ffb6fa49b471babf07f0ad769a) Chore: Add rel/abs path tests in `no-restricted-{imports/modules}` rules (#14910) (Bryan Mishkin) +* [`62c6fe7`](https://github.com/eslint/eslint/commit/62c6fe7d10ff4eeebd196e143f96cfd88818393d) Upgrade: Debug 4.0.1 > 4.3.2 (#14892) (sandesh bafna) +* [`f984515`](https://github.com/eslint/eslint/commit/f98451584a82e41f82ceacd484ea0fe90aa9ce63) Chore: add assertions on reporting location in `semi` (#14899) (Nitin Kumar) +* [`a773b99`](https://github.com/eslint/eslint/commit/a773b99873965652a86bec489193dc42a8923f5f) Fix: no-useless-computed-key edge cases with class fields (refs #14857) (#14903) (Milos Djermanovic) +* [`88db3f5`](https://github.com/eslint/eslint/commit/88db3f54988dddfbda35764ecf1ea16354c4213a) Upgrade: `js-yaml` to v4 (#14890) (Bryan Mishkin) +* [`cbc43da`](https://github.com/eslint/eslint/commit/cbc43daad2ea229fb15a9198efd2bc2721dfb75f) Fix: prefer-destructuring PrivateIdentifier false positive (refs #14857) (#14897) (Milos Djermanovic) +* [`ccb9a91`](https://github.com/eslint/eslint/commit/ccb9a9138acd63457e004630475495954c1be6f4) Fix: dot-notation false positive with private identifier (refs #14857) (#14898) (Milos Djermanovic) +* [`8c35066`](https://github.com/eslint/eslint/commit/8c350660e61284c41a5cc1a5955c858db53c516b) Sponsors: Sync README with website (ESLint Jenkins) +* [`a3dd825`](https://github.com/eslint/eslint/commit/a3dd8257252f392de5cf793c36ecab2acd955659) Sponsors: Sync README with website (ESLint Jenkins) +* [`c4e5802`](https://github.com/eslint/eslint/commit/c4e58023f22381508babfc52087853b5e3965b9c) Docs: improve rule details for `no-console` (fixes #14793) (#14901) (Nitin Kumar) +* [`9052eee`](https://github.com/eslint/eslint/commit/9052eee07a459dc059cd92f657a3ae73acc95bb5) Update: check class fields in no-extra-parens (refs #14857) (#14906) (Milos Djermanovic) +* [`5c3a470`](https://github.com/eslint/eslint/commit/5c3a47072aeb5cfda40a1eb20b43a10c5ca7aab3) Docs: add class fields in no-multi-assign documentation (refs #14857) (#14907) (Milos Djermanovic) +* [`d234d89`](https://github.com/eslint/eslint/commit/d234d890b383837f8e4bda0f6ce1e2a348f9835e) Docs: add class fields in func-names documentation (refs #14857) (#14908) (Milos Djermanovic) +* [`ae6072b`](https://github.com/eslint/eslint/commit/ae6072b1de5c8b30ce6c58290852082439c40b30) Upgrade: `eslint-visitor-keys` to v3 (#14902) (Bryan Mishkin) +* [`e53d8cf`](https://github.com/eslint/eslint/commit/e53d8cf9d73bd105cf6ba4f6b5477ccc4b980939) Upgrade: `markdownlint` dev dependencies (#14883) (Bryan Mishkin) +* [`d66e941`](https://github.com/eslint/eslint/commit/d66e9414be60e05badb96bc3e1a55ca34636d7f8) Upgrade: @humanwhocodes/config-array to 0.6 (#14891) (Bryan Mishkin) +* [`149230c`](https://github.com/eslint/eslint/commit/149230ce7e296c029a0b6c085216fc0360ed4c65) Chore: Specify Node 14.x for Verify Files CI job (#14896) (Milos Djermanovic) +* [`537cf6a`](https://github.com/eslint/eslint/commit/537cf6a0e78ee9b7167e7f8c56f4053d3fb5b2d7) Chore: update `glob-parent` (fixes #14879)(#14887) (Nitin Kumar) +* [`f7b4a3f`](https://github.com/eslint/eslint/commit/f7b4a3f6a44e167c71985d373f73eebd3a4d9556) Chore: update dev deps to latest (#14624) (薛定谔的猫) +* [`24c9f2a`](https://github.com/eslint/eslint/commit/24c9f2ac57efcd699ca69695c82e51ce5742df7b) Breaking: Strict package exports (refs #13654) (#14706) (Nicholas C. Zakas) +* [`86d31a4`](https://github.com/eslint/eslint/commit/86d31a4951e3a39e359e284f5fe336ac477369fe) Breaking: disallow SourceCode#getComments() in RuleTester (refs #14744) (#14769) (Milos Djermanovic) +* [`1d2213d`](https://github.com/eslint/eslint/commit/1d2213deb69c5901c1950bbe648aa819e7e742ed) Breaking: Fixable disable directives (fixes #11815) (#14617) (Josh Goldberg) +* [`4a7aab7`](https://github.com/eslint/eslint/commit/4a7aab7d4323ff7027eebca709d4e95a9aaa80bc) Breaking: require `meta` for fixable rules (fixes #13349) (#14634) (Milos Djermanovic) +* [`d6a761f`](https://github.com/eslint/eslint/commit/d6a761f9b6582e9f71705161be827ca303ef183f) Breaking: Require `meta.hasSuggestions` for rules with suggestions (#14573) (Bryan Mishkin) +* [`4c841b8`](https://github.com/eslint/eslint/commit/4c841b880b5649392a55c98ecc9af757bd213ff0) Breaking: allow all directives in line comments (fixes #14575) (#14656) (薛定谔的猫) +* [`6bd747b`](https://github.com/eslint/eslint/commit/6bd747b5b7731195224875b952a9ea61445a9938) Breaking: support new regex d flag (fixes #14640) (#14653) (Yosuke Ota) +* [`8b4f3ab`](https://github.com/eslint/eslint/commit/8b4f3abdb794feb3be31959bb44bfb0ef6318e8e) Breaking: fix comma-dangle schema (fixes #13739) (#14030) (Joakim Nilsson) +* [`b953a4e`](https://github.com/eslint/eslint/commit/b953a4ee12f120658a9ec27d1f8ca88dd3dfb599) Breaking: upgrade espree and support new class features (refs #14343) (#14591) (Toru Nagashima) +* [`8cce06c`](https://github.com/eslint/eslint/commit/8cce06cb39886902ce0d2e6882f46c3bf52fb955) Breaking: add some rules to eslint:recommended (refs #14673) (#14691) (薛定谔的猫) +* [`86bb63b`](https://github.com/eslint/eslint/commit/86bb63b370e0ff350e988a5fa228a8234abe800c) Breaking: Drop `codeframe` and `table` formatters (#14316) (Federico Brigante) +* [`f3cb320`](https://github.com/eslint/eslint/commit/f3cb3208c8952a6218d54658cfda85942b9fda42) Breaking: drop node v10/v13/v15 (fixes #14023) (#14592) (薛定谔的猫) +* [`b8b2d55`](https://github.com/eslint/eslint/commit/b8b2d5553b0de23e8b72ee45949650cd5f9a10d2) Build: add codeql (#14729) (薛定谔的猫) +* [`e037d61`](https://github.com/eslint/eslint/commit/e037d61a12ad17a36e05dcf65aa63fad303c79b9) Docs: Mention workaround for escaping the slash character in selectors (#14675) (Aria) +* [`81f03b6`](https://github.com/eslint/eslint/commit/81f03b6ad69c7f67ad6ba72e02e73266aa8f7696) Docs: Update license copyright (#14877) (Nicholas C. Zakas) +* [`fa1c07c`](https://github.com/eslint/eslint/commit/fa1c07c0d65ce21a30f5bb4a9f2ac511f8df6446) Sponsors: Sync README with website (ESLint Jenkins) +* [`e31f492`](https://github.com/eslint/eslint/commit/e31f49206f94e2b3977ec37892d4b87ab1e46872) Sponsors: Sync README with website (ESLint Jenkins) +* [`8307256`](https://github.com/eslint/eslint/commit/83072561b006a558d026c5a507f92945b821a0cd) Sponsors: Sync README with website (ESLint Jenkins) + +v7.32.0 - July 30, 2021 + +* [`3c78a7b`](https://github.com/eslint/eslint/commit/3c78a7bff6044fd196ae3b737983e6744c6eb7c8) Chore: Adopt `eslint-plugin/prefer-message-ids` rule internally (#14841) (Bryan Mishkin) +* [`faecf56`](https://github.com/eslint/eslint/commit/faecf56cdb4146b28bfa4f1980adb41b4d3614b1) Update: change reporting location for `curly` rule (refs #12334) (#14766) (Nitin Kumar) +* [`d7dc07a`](https://github.com/eslint/eslint/commit/d7dc07a15e256cee9232183165e2f6102f2c0873) Fix: ignore lines with empty elements (fixes #12756) (#14837) (Soufiane Boutahlil) +* [`1bfbefd`](https://github.com/eslint/eslint/commit/1bfbefdaaf19ef32df42b89a3f5d32cff1e5b831) New: Exit on fatal error (fixes #13711) (#14730) (Antonios Katopodis) +* [`ed007c8`](https://github.com/eslint/eslint/commit/ed007c82ee9d2170c87500d98303554b5f90b915) Chore: Simplify internal `no-invalid-meta` rule (#14842) (Bryan Mishkin) +* [`d53d906`](https://github.com/eslint/eslint/commit/d53d9064b9dd0dd6a8ea39e07b16310c8364db69) Docs: Prepare data for website to indicate rules with suggestions (#14830) (Bryan Mishkin) +* [`d28f2ff`](https://github.com/eslint/eslint/commit/d28f2ffb986e49d6da5c1d91215580591f4cfd35) Docs: Reference eslint-config-eslint to avoid potential for staleness (#14805) (Brett Zamir) +* [`8be8a36`](https://github.com/eslint/eslint/commit/8be8a36010145dfcd31cbdd4f781a91989e3b1bd) Chore: Adopt `eslint-plugin/require-meta-docs-url` rule internally (#14823) (Bryan Mishkin) +* [`f9c164f`](https://github.com/eslint/eslint/commit/f9c164f7b74ca73384c8c80eed5bdbe359b44f6c) Docs: New syntax issue template (#14826) (Nicholas C. Zakas) +* [`eba0c45`](https://github.com/eslint/eslint/commit/eba0c4595c126a91f700d5f2e8723ec3f820a830) Chore: assertions on reporting loc in `unicode-bom` (refs #12334) (#14809) (Nitin Kumar) +* [`ed945bd`](https://github.com/eslint/eslint/commit/ed945bd662714b1917e9de71d5b322a28be9161b) Docs: fix multiple broken links (#14833) (Sam Chen) +* [`60df44c`](https://github.com/eslint/eslint/commit/60df44c79b0f74406119c0c040a360ca84e721fc) Chore: use `actions/setup-node@v2` (#14816) (Nitin Kumar) +* [`6641d88`](https://github.com/eslint/eslint/commit/6641d88e17d952a8e51df5e0d3882a842d4c3f35) Docs: Update README team and sponsors (ESLint Jenkins) + +v7.31.0 - July 17, 2021 + +* [`efdbb12`](https://github.com/eslint/eslint/commit/efdbb1227019427ec2d968a8d6e9151dd8a77c35) Upgrade: @eslint/eslintrc to v0.4.3 (#14808) (Brandon Mills) +* [`a96b05f`](https://github.com/eslint/eslint/commit/a96b05f6c5649cfee112d605c91d95aa191e2f78) Update: add end location to report in `consistent-return` (refs #12334) (#14798) (Nitin Kumar) +* [`e0e8e30`](https://github.com/eslint/eslint/commit/e0e8e308929c9c66612505f2da89043f8592eea7) Docs: update BUG_REPORT template (#14787) (Nitin Kumar) +* [`39115c8`](https://github.com/eslint/eslint/commit/39115c8b71d2629161359f6456f47fdbd552fddd) Docs: provide more context to no-eq-null (#14801) (gfyoung) +* [`9a3c73c`](https://github.com/eslint/eslint/commit/9a3c73c130d437a65f4edba0dcb63390e68cac41) Docs: fix a broken link (#14790) (Sam Chen) +* [`ddffa8a`](https://github.com/eslint/eslint/commit/ddffa8ad58b4b124b08061e9045fdb5370cbdbe3) Update: Indicating the operator in question (#14764) (Paul Smith) +* [`bba714c`](https://github.com/eslint/eslint/commit/bba714c2ed813821ed288fbc07722cdde6e534fe) Update: Clarifying what changes need to be made in no-mixed-operators (#14765) (Paul Smith) +* [`b0d22e3`](https://github.com/eslint/eslint/commit/b0d22e3eff18ea7f08189134c07cddceaec69a09) Docs: Mention benefit of providing `meta.docs.url` (#14774) (Bryan Mishkin) +* [`000cc79`](https://github.com/eslint/eslint/commit/000cc796fd487e7b9ba8bcc5857dd691044479cc) Sponsors: Sync README with website (ESLint Jenkins) +* [`a6a7438`](https://github.com/eslint/eslint/commit/a6a7438502abc6a1e29ec35cfbe2058ffc0803b1) Chore: pin fs-teardown@0.1.1 (#14771) (Milos Djermanovic) + +v7.30.0 - July 2, 2021 + +* [`5f74642`](https://github.com/eslint/eslint/commit/5f746420700d457b92dd86659de588d272937b79) Chore: don't check Program.start in SourceCode#getComments (refs #14744) (#14748) (Milos Djermanovic) +* [`19a871a`](https://github.com/eslint/eslint/commit/19a871a35ae9997ce352624b1081c96c54b73a9f) Docs: Suggest linting plugins for ESLint plugin developers (#14754) (Bryan Mishkin) +* [`aa87329`](https://github.com/eslint/eslint/commit/aa87329d919f569404ca573b439934552006572f) Docs: fix broken links (#14756) (Sam Chen) +* [`278813a`](https://github.com/eslint/eslint/commit/278813a6e759f6b5512ac64c7530c9c51732e692) Docs: fix and add more examples for new-cap rule (fixes #12874) (#14725) (Nitin Kumar) +* [`ed1da5d`](https://github.com/eslint/eslint/commit/ed1da5d96af2587b7211854e45cf8657ef808710) Update: ecmaVersion allows "latest" (#14720) (薛定谔的猫) +* [`104c0b5`](https://github.com/eslint/eslint/commit/104c0b592f203d315a108d311c58375357e40b24) Update: improve use-isnan rule to detect `Number.NaN` (fixes #14715) (#14718) (Nitin Kumar) +* [`b08170b`](https://github.com/eslint/eslint/commit/b08170b92beb22db6ec612ebdfff930f9e0582ab) Update: Implement FlatConfigArray (refs #13481) (#14321) (Nicholas C. Zakas) +* [`f113cdd`](https://github.com/eslint/eslint/commit/f113cdd872257d72bbd66d95e4eaf13623323b24) Chore: upgrade eslint-plugin-eslint-plugin (#14738) (薛定谔的猫) +* [`1b8997a`](https://github.com/eslint/eslint/commit/1b8997ab63781f4ebf87e3269400b2ef4c7d2973) Docs: Fix getRulesMetaForResults link syntax (#14723) (Brandon Mills) +* [`aada733`](https://github.com/eslint/eslint/commit/aada733d2aee830aa32cccb9828cd72db4ccd6bd) Docs: fix two broken links (#14726) (Sam Chen) +* [`8972529`](https://github.com/eslint/eslint/commit/8972529f82d13bd04059ee8852b4ebb9b5350962) Docs: Update README team and sponsors (ESLint Jenkins) + +v7.29.0 - June 18, 2021 + +* [`bfbfe5c`](https://github.com/eslint/eslint/commit/bfbfe5c1fd4c39a06d5e159dbe48479ca4305fc0) New: Add only to RuleTester (refs eslint/rfcs#73) (#14677) (Brandon Mills) +* [`c2cd7b4`](https://github.com/eslint/eslint/commit/c2cd7b4a18057ca6067bdfc16de771dc5d90c0ea) New: Add ESLint#getRulesMetaForResults() (refs #13654) (#14716) (Nicholas C. Zakas) +* [`eea7e0d`](https://github.com/eslint/eslint/commit/eea7e0d09d6ef43d6663cbe424e7974764a5f7fe) Chore: remove duplicate code (#14719) (Nitin Kumar) +* [`6a1c7a0`](https://github.com/eslint/eslint/commit/6a1c7a0dac050ea5876972c50563a7eb867b38d3) Fix: allow fallthrough comment inside block (fixes #14701) (#14702) (Kevin Gibbons) +* [`a47e5e3`](https://github.com/eslint/eslint/commit/a47e5e30b0da364593b6881f6826c595da8696f5) Docs: Add Mega-Linter to the list of integrations (#14707) (Nicolas Vuillamy) +* [`353ddf9`](https://github.com/eslint/eslint/commit/353ddf965078030794419b089994373e27ffc86e) Chore: enable reportUnusedDisableDirectives in eslint-config-eslint (#14699) (薛定谔的猫) +* [`757c495`](https://github.com/eslint/eslint/commit/757c49584a5852c468c1b4a0b74ad3aa39d954e5) Chore: add some rules to eslint-config-eslint (#14692) (薛定谔的猫) +* [`c93a222`](https://github.com/eslint/eslint/commit/c93a222563177a9b5bc7a59aa106bc0a6d31e063) Docs: fix a broken link (#14697) (Sam Chen) +* [`655c118`](https://github.com/eslint/eslint/commit/655c1187fc845bac61ae8d06c556f1a59ee2071b) Sponsors: Sync README with website (ESLint Jenkins) +* [`e2bed2e`](https://github.com/eslint/eslint/commit/e2bed2ead22b575d55ccaeed94eecd3a979dd871) Sponsors: Sync README with website (ESLint Jenkins) +* [`8490fb4`](https://github.com/eslint/eslint/commit/8490fb42e559ef0b3c34ac60be4e05e0d879a9cb) Sponsors: Sync README with website (ESLint Jenkins) +* [`ddbe877`](https://github.com/eslint/eslint/commit/ddbe877c95224e127215d35562a175c6f2b7ba22) Sponsors: Sync README with website (ESLint Jenkins) + v7.28.0 - June 4, 2021 * [`1237705`](https://github.com/eslint/eslint/commit/1237705dd08c209c5e3136045ec51a4ba87a3abe) Upgrade: @eslint/eslintrc to 0.4.2 (#14672) (Milos Djermanovic) diff --git a/eslint/LICENSE b/eslint/LICENSE index 7fe552a..b607bb3 100644 --- a/eslint/LICENSE +++ b/eslint/LICENSE @@ -1,4 +1,4 @@ -Copyright JS Foundation and other contributors, https://js.foundation +Copyright OpenJS Foundation and other contributors, Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/eslint/Makefile.js b/eslint/Makefile.js index 6cffb77..0a0846b 100644 --- a/eslint/Makefile.js +++ b/eslint/Makefile.js @@ -3,16 +3,13 @@ * @author nzakas */ -/* global target */ -/* eslint no-use-before-define: "off", no-console: "off" */ +/* eslint no-use-before-define: "off", no-console: "off" -- CLI */ "use strict"; //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ -require("shelljs/make"); - const checker = require("npm-license"), ReleaseOps = require("eslint-release"), dateformat = require("dateformat"), @@ -28,6 +25,13 @@ const checker = require("npm-license"), { CLIEngine } = require("./lib/cli-engine"), builtinRules = require("./lib/rules/index"); +require("shelljs/make"); +/* global target -- global.target is declared in `shelljs/make.js` */ +/** + * global.target = {}; + * @see https://github.com/shelljs/shelljs/blob/124d3349af42cb794ae8f78fc9b0b538109f7ca7/make.js#L4 + * @see https://github.com/DefinitelyTyped/DefinitelyTyped/blob/3aa2d09b6408380598cfb802743b07e1edb725f3/types/shelljs/make.d.ts#L8-L11 + */ const { cat, cd, cp, echo, exec, exit, find, ls, mkdir, pwd, rm, test } = require("shelljs"); //------------------------------------------------------------------------------ @@ -43,7 +47,7 @@ const { cat, cd, cp, echo, exec, exit, find, ls, mkdir, pwd, rm, test } = requir const PERF_MULTIPLIER = 13e6; const OPEN_SOURCE_LICENSES = [ - /MIT/u, /BSD/u, /Apache/u, /ISC/u, /WTF/u, /Public Domain/u, /LGPL/u + /MIT/u, /BSD/u, /Apache/u, /ISC/u, /WTF/u, /Public Domain/u, /LGPL/u, /Python/u ]; //------------------------------------------------------------------------------ @@ -66,7 +70,8 @@ const NODE = "node ", // intentional extra space // Files RULE_FILES = glob.sync("lib/rules/*.js").filter(filePath => path.basename(filePath) !== "index.js"), JSON_FILES = find("conf/").filter(fileType("json")), - MARKDOWN_FILES_ARRAY = find("docs/").concat(ls(".")).filter(fileType("md")), + MARKDOWNLINT_IGNORED_FILES = fs.readFileSync(path.join(__dirname, ".markdownlintignore"), "utf-8").split("\n"), + MARKDOWN_FILES_ARRAY = find("docs/").concat(ls(".")).filter(fileType("md")).filter(file => !MARKDOWNLINT_IGNORED_FILES.includes(file)), TEST_FILES = "\"tests/{bin,lib,tools}/**/*.js\"", PERF_ESLINTRC = path.join(PERF_TMP_DIR, "eslintrc.yml"), PERF_MULTIFILES_TARGET_DIR = path.join(PERF_TMP_DIR, "eslint"), @@ -148,8 +153,8 @@ function generateBlogPost(releaseInfo, prereleaseMajorVersion) { /** * Generates a doc page with formatter result examples - * @param {Object} formatterInfo Linting results from each formatter - * @param {string} [prereleaseVersion] The version used for a prerelease. This + * @param {Object} formatterInfo Linting results from each formatter + * @param {string} [prereleaseVersion] The version used for a prerelease. This * changes where the output is stored. * @returns {void} */ @@ -176,8 +181,8 @@ function generateFormatterExamples(formatterInfo, prereleaseVersion) { */ function generateRuleIndexPage() { const outputFile = "../website/_data/rules.yml", - categoryList = "conf/category-list.json", - categoriesData = JSON.parse(cat(path.resolve(categoryList))); + ruleTypes = "conf/rule-type-list.json", + ruleTypesData = JSON.parse(cat(path.resolve(ruleTypes))); RULE_FILES .map(filename => [filename, path.basename(filename, ".js")]) @@ -188,7 +193,7 @@ function generateRuleIndexPage() { const rule = require(path.resolve(filename)); if (rule.meta.deprecated) { - categoriesData.deprecated.rules.push({ + ruleTypesData.deprecated.rules.push({ name: basename, replacedBy: rule.meta.replacedBy || [] }); @@ -197,22 +202,23 @@ function generateRuleIndexPage() { name: basename, description: rule.meta.docs.description, recommended: rule.meta.docs.recommended || false, - fixable: !!rule.meta.fixable + fixable: !!rule.meta.fixable, + hasSuggestions: !!rule.meta.hasSuggestions }, - category = categoriesData.categories.find(c => c.name === rule.meta.docs.category); + ruleType = ruleTypesData.types.find(c => c.name === rule.meta.type); - if (!category.rules) { - category.rules = []; + if (!ruleType.rules) { + ruleType.rules = []; } - category.rules.push(output); + ruleType.rules.push(output); } }); // `.rules` will be `undefined` if all rules in category are deprecated. - categoriesData.categories = categoriesData.categories.filter(category => !!category.rules); + ruleTypesData.types = ruleTypesData.types.filter(ruleType => !!ruleType.rules); - const output = yaml.safeDump(categoriesData, { sortKeys: true }); + const output = yaml.dump(ruleTypesData, { sortKeys: true }); output.to(outputFile); } @@ -388,7 +394,7 @@ function getFirstVersionOfDeletion(filePath) { * @private */ function lintMarkdown(files) { - const config = yaml.safeLoad(fs.readFileSync(path.join(__dirname, "./.markdownlint.yml"), "utf8")), + const config = yaml.load(fs.readFileSync(path.join(__dirname, "./.markdownlint.yml"), "utf8")), result = markdownlint.sync({ files, config, @@ -471,10 +477,6 @@ function getBinFile(command) { // Tasks //------------------------------------------------------------------------------ -target.all = function() { - target.test(); -}; - target.lint = function([fix = false] = []) { let errors = 0, lastReturn; @@ -544,7 +546,7 @@ target.mocha = () => { echo("Running unit tests"); - lastReturn = exec(`${getBinFile("nyc")} -- ${MOCHA} -R progress -t ${MOCHA_TIMEOUT} -c ${TEST_FILES}`); + lastReturn = exec(`${getBinFile("nyc")} -- ${MOCHA} --forbid-only -R progress -t ${MOCHA_TIMEOUT} -c ${TEST_FILES}`); if (lastReturn.code !== 0) { errors++; } @@ -580,12 +582,6 @@ target.test = function() { target.checkLicenses(); }; -target.docs = function() { - echo("Generating documentation"); - exec(`${getBinFile("jsdoc")} -d jsdoc lib`); - echo("Documentation has been output to /jsdoc"); -}; - target.gensite = function(prereleaseVersion) { echo("Generating eslint.org"); @@ -636,10 +632,12 @@ target.gensite = function(prereleaseVersion) { }; } - const rules = require(".").linter.getRules(); + const { Linter } = require("."); + const rules = new Linter().getRules(); const RECOMMENDED_TEXT = "\n\n(recommended) The `\"extends\": \"eslint:recommended\"` property in a configuration file enables this rule."; const FIXABLE_TEXT = "\n\n(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule."; + const HAS_SUGGESTIONS_TEXT = "\n\n(hasSuggestions) Some problems reported by this rule are manually fixable by editor [suggestions](../developer-guide/working-with-rules#providing-suggestions)."; // 4. Loop through all files in temporary directory process.stdout.write("> Updating files (Steps 4-9): 0/... - ...\r"); @@ -649,8 +647,9 @@ target.gensite = function(prereleaseVersion) { tempFiles.forEach((filename, i) => { if (test("-f", filename) && path.extname(filename) === ".md") { - const rulesUrl = "https://github.com/eslint/eslint/tree/master/lib/rules/", - docsUrl = "https://github.com/eslint/eslint/tree/master/docs/rules/", + const rulesUrl = "https://github.com/eslint/eslint/tree/HEAD/lib/rules/", + testsUrl = "https://github.com/eslint/eslint/tree/HEAD/tests/lib/rules/", + docsUrl = "https://github.com/eslint/eslint/tree/HEAD/docs/rules/", baseName = path.basename(filename), sourceBaseName = `${path.basename(filename, ".md")}.js`, sourcePath = path.join("lib/rules", sourceBaseName), @@ -669,13 +668,14 @@ target.gensite = function(prereleaseVersion) { const rule = rules.get(ruleName); const isRecommended = rule && rule.meta.docs.recommended; const isFixable = rule && rule.meta.fixable; + const hasSuggestions = rule && rule.meta.hasSuggestions; // Incorporate the special portion into the documentation content const textSplit = text.split("\n"); const ruleHeading = textSplit[0]; const ruleDocsContent = textSplit.slice(1).join("\n"); - text = `${ruleHeading}${isRecommended ? RECOMMENDED_TEXT : ""}${isFixable ? FIXABLE_TEXT : ""}\n${ruleDocsContent}`; + text = `${ruleHeading}${isRecommended ? RECOMMENDED_TEXT : ""}${isFixable ? FIXABLE_TEXT : ""}${hasSuggestions ? HAS_SUGGESTIONS_TEXT : ""}\n${ruleDocsContent}`; title = `${ruleName} - Rules`; if (rule && rule.meta) { @@ -696,7 +696,7 @@ target.gensite = function(prereleaseVersion) { "---", `title: ${title}`, "layout: doc", - `edit_link: https://github.com/eslint/eslint/edit/master/${filePath}`, + `edit_link: https://github.com/eslint/eslint/edit/main/${filePath}`, ruleType, "---", "", @@ -732,6 +732,7 @@ target.gensite = function(prereleaseVersion) { text += "\n## Resources\n\n"; if (!removed) { text += `* [Rule source](${rulesUrl}${sourceBaseName})\n`; + text += `* [Test source](${testsUrl}${sourceBaseName})\n`; } text += `* [Documentation source](${docsUrl}${baseName})\n`; } @@ -817,6 +818,20 @@ target.checkRuleFiles = function() { return idNewAtBeginningOfTitleRegExp.test(docText) || idOldAtEndOfTitleRegExp.test(docText); } + /** + * Check if deprecated information is in rule code and READNE.md. + * @returns {boolean} true if present + * @private + */ + function hasDeprecatedInfo() { + const ruleCode = cat(filename); + const deprecatedTagRegExp = /@deprecated in ESLint/u; + const docText = cat(docFilename); + const deprecatedInfoRegExp = /This rule was .+deprecated.+in ESLint/u; + + return deprecatedTagRegExp.test(ruleCode) && deprecatedInfoRegExp.test(docText); + } + // check for docs if (!test("-f", docFilename)) { console.error("Missing documentation for rule %s", basename); @@ -843,12 +858,17 @@ target.checkRuleFiles = function() { if (!ruleDef) { console.error(`Missing rule from index (./lib/rules/index.js): ${basename}. If you just added a new rule then add an entry for it in this file.`); errors++; - } + } else { - // check eslint:recommended - const recommended = require("./conf/eslint-recommended"); + // check deprecated + if (ruleDef.meta.deprecated && !hasDeprecatedInfo()) { + console.error(`Missing deprecated information in ${basename} rule code or README.md. Please write @deprecated tag in code or 「This rule was deprecated in ESLint ...」 in README.md.`); + errors++; + } + + // check eslint:recommended + const recommended = require("./conf/eslint-recommended"); - if (ruleDef) { if (ruleDef.meta.docs.recommended) { if (recommended.rules[basename] !== "error") { console.error(`Missing rule from eslint:recommended (./conf/eslint-recommended.js): ${basename}. If you just made a rule recommended then add an entry for it in this file.`); @@ -957,14 +977,20 @@ function createConfigForPerformanceTest() { content.join("\n").to(PERF_ESLINTRC); } +/** + * @callback TimeCallback + * @param {?int[]} results + * @returns {void} + */ + /** * Calculates the time for each run for performance * @param {string} cmd cmd * @param {int} runs Total number of runs to do * @param {int} runNumber Current run number * @param {int[]} results Collection results from each run - * @param {Function} cb Function to call when everything is done - * @returns {int[]} calls the cb with all the results + * @param {TimeCallback} cb Function to call when everything is done + * @returns {void} calls the cb with all the results * @private */ function time(cmd, runs, runNumber, results, cb) { @@ -1076,9 +1102,12 @@ target.perf = function() { // Count test target files. const count = glob.sync( - process.platform === "win32" - ? PERF_MULTIFILES_TARGETS.slice(2).replace(/\\/gu, "/") - : PERF_MULTIFILES_TARGETS + ( + process.platform === "win32" + ? PERF_MULTIFILES_TARGETS.replace(/\\/gu, "/") + : PERF_MULTIFILES_TARGETS + ) + .slice(1, -1) // strip quotes ).length; runPerformanceTest( diff --git a/eslint/README.md b/eslint/README.md index 438a087..bbc74b7 100644 --- a/eslint/README.md +++ b/eslint/README.md @@ -43,7 +43,7 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J ## Installation and Usage -Prerequisites: [Node.js](https://nodejs.org/) (`^10.12.0`, or `>=12.0.0`) built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.) +Prerequisites: [Node.js](https://nodejs.org/) (`^12.22.0`, `^14.17.0`, or `>=16.0.0`) built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.) You can install ESLint using npm: @@ -123,7 +123,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, 2019, and 2020. You can set your desired ECMAScript syntax (and other settings, like global variables or your target environments) through [configuration](https://eslint.org/docs/user-guide/configuring). +ESLint has full support for ECMAScript 3, 5 (default), 2015, 2016, 2017, 2018, 2019, 2020, 2021 and 2022. You can set your desired ECMAScript syntax (and other settings, like global variables or your target environments) through [configuration](https://eslint.org/docs/user-guide/configuring). ### What about experimental features? @@ -206,6 +206,9 @@ This means: These folks keep the project moving and are resources for help. + + + ### Technical Steering Committee (TSC) @@ -242,7 +245,7 @@ Toru Nagashima
-薛定谔的猫 +唯然
@@ -254,6 +257,16 @@ Toru Nagashima The people who review and fix bugs and help triage issues.
+ +
+Brett Zamir +
+
+ +
+Bryan Mishkin +
+

Pig Fang @@ -268,11 +281,19 @@ Anix
YeonJuan
+
+ +
+Nitin Kumar +
+ + + ## Sponsors The following companies, organizations, and individuals support ESLint's ongoing maintenance and development. [Become a Sponsor](https://opencollective.com/eslint) to get your logo on our README and website. @@ -281,9 +302,9 @@ The following companies, organizations, and individuals support ESLint's ongoing

Platinum Sponsors

Automattic

Gold Sponsors

-

Nx (by Nrwl) Chrome's Web Framework & Tools Performance Fund Salesforce Airbnb Substack

Silver Sponsors

-

Retool Liftoff

Bronze Sponsors

-

Anagram Solver Bugsnag Stability Monitoring Mixpanel VPS Server Icons8: free icons, photos, illustrations, and music Discord ThemeIsle Fire Stick Tricks

+

Nx (by Nrwl) Chrome's Web Framework & Tools Performance Fund Salesforce Airbnb Coinbase American Express Substack

Silver Sponsors

+

Liftoff

Bronze Sponsors

+

launchdarkly TROYPOINT Anagram Solver VPS Server Icons8: free icons, photos, illustrations, and music Discord ThemeIsle Fire Stick Tricks Practice Ignition

## Technology Sponsors diff --git a/eslint/bin/eslint.js b/eslint/bin/eslint.js index 5fa5766..6b05356 100755 --- a/eslint/bin/eslint.js +++ b/eslint/bin/eslint.js @@ -5,7 +5,7 @@ * @author Nicholas C. Zakas */ -/* eslint no-console:off */ +/* eslint no-console:off -- CLI */ "use strict"; diff --git a/eslint/conf/category-list.json b/eslint/conf/rule-type-list.json similarity index 74% rename from eslint/conf/category-list.json rename to eslint/conf/rule-type-list.json index cd3b816..f362aa4 100644 --- a/eslint/conf/category-list.json +++ b/eslint/conf/rule-type-list.json @@ -1,11 +1,8 @@ { - "categories": [ - { "name": "Possible Errors", "description": "These rules relate to possible syntax or logic errors in JavaScript code:" }, - { "name": "Best Practices", "description": "These rules relate to better ways of doing things to help you avoid problems:" }, - { "name": "Strict Mode", "description": "These rules relate to strict mode directives:" }, - { "name": "Variables", "description": "These rules relate to variable declarations:" }, - { "name": "Stylistic Issues", "description": "These rules relate to style guidelines, and are therefore quite subjective:" }, - { "name": "ECMAScript 6", "description": "These rules relate to ES6, also known as ES2015:" } + "types": [ + { "name": "problem", "displayName": "Possible Problems", "description": "These rules relate to possible logic errors in code:" }, + { "name": "suggestion", "displayName": "Suggestions", "description": "These rules suggest alternate ways of doing things:" }, + { "name": "layout", "displayName": "Layout & Formatting", "description": "These rules care about how the code looks rather than how it executes:" } ], "deprecated": { "name": "Deprecated", diff --git a/eslint/docs/developer-guide/README.md b/eslint/docs/developer-guide/README.md index ea19430..a5e6592 100644 --- a/eslint/docs/developer-guide/README.md +++ b/eslint/docs/developer-guide/README.md @@ -32,7 +32,7 @@ You're finally ready to start working with rules. You may want to fix an existin ## Section 5: [Working with Plugins](working-with-plugins.md) -You've developed library-specific rules for ESLint and you want to share it with the community. You can publish an ESLint plugin on npm. +You've developed library-specific rules for ESLint and you want to share them with the community. You can publish an ESLint plugin on npm. ## Section 6: [Working with Custom Parsers](working-with-custom-parsers.md) diff --git a/eslint/docs/developer-guide/architecture.md b/eslint/docs/developer-guide/architecture.md index 1ea74bb..6531c28 100644 --- a/eslint/docs/developer-guide/architecture.md +++ b/eslint/docs/developer-guide/architecture.md @@ -5,7 +5,7 @@ At a high level, there are a few key parts to ESLint: * `bin/eslint.js` - this is the file that actually gets executed with the command line utility. It's a dumb wrapper that does nothing more than bootstrap ESLint, passing the command line arguments to `cli`. This is intentionally small so as not to require heavy testing. -* `lib/api.js` - this is the entry point of `require("eslint")`. This file exposes an object that contains public classes `Linter`, `CLIEngine`, `RuleTester`, and `SourceCode`. +* `lib/api.js` - this is the entry point of `require("eslint")`. This file exposes an object that contains public classes `Linter`, `ESLint`, `RuleTester`, and `SourceCode`. * `lib/cli.js` - this is the heart of the ESLint CLI. It takes an array of arguments and then uses `eslint` to execute the commands. By keeping this as a separate utility, it allows others to effectively call ESLint from within another Node.js program as if it were done on the command line. The main call is `cli.execute()`. This is also the part that does all the file reading, directory traversing, input, and output. * `lib/init/` - this module contains `--init` functionality that set up a configuration file for end users. * `lib/cli-engine/` - this module is `CLIEngine` class that finds source code files and configuration files then does code verifying with the `Linter` class. This includes the loading logic of configuration files, parsers, plugins, and formatters. diff --git a/eslint/docs/developer-guide/code-conventions.md b/eslint/docs/developer-guide/code-conventions.md index 3ee0228..0c1cbe6 100644 --- a/eslint/docs/developer-guide/code-conventions.md +++ b/eslint/docs/developer-guide/code-conventions.md @@ -1,925 +1,9 @@ # Code Conventions -Programming language style guides are important for the long-term maintainability of software. This guide is based on the [Code Conventions for the Java Programming Language](https://java.sun.com/docs/codeconv/) and [Douglas Crockford's Code Conventions for the JavaScript Programming Language](http://javascript.crockford.com/code.html). Modifications have been made due to my personal experience and preferences. +Code conventions for ESLint are determined by +[eslint-config-eslint](https://www.npmjs.com/package/eslint-config-eslint). -## File Format - -Each file has this same basic format: - -```js -/** - * @fileoverview Description of the file - * @author Your Name - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -// require() statements - -//------------------------------------------------------------------------------ -// Helpers -//------------------------------------------------------------------------------ - -// private methods/data - -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ - -// exported objects/methods -module.exports = { - -}; -``` - -The `@author` field gives you credit for having created the file. - -## Indentation - -Each indentation level is made up of four spaces. Do not use tabs. - - // Good - if (true) { - doSomething(); - } - -## Primitive Literals - -Strings should always use double quotes (never single quotes) and should always appear on a single line. Never use a slash to create a new line in a string. - - // Good - var name = "Nicholas"; - - // Bad: Single quotes - var name = 'Nicholas'; - - // Bad: Wrapping to second line - var longString = "Here's the story, of a man \ - named Brady."; - -Numbers should be written as decimal integers, e-notation integers, hexadecimal integers or floating-point decimals with at least one digit before and one digit after the decimal point. Never use octal literals. - - // Good - var count = 10; - - // Good - var price = 10.0; - var price = 10.00; - - // Good - var num = 0xA2; - - // Good - var num = 1e23; - - // Bad: Hanging decimal point - var price = 10.; - - // Bad: Leading decimal point - var price = .1; - - // Bad: Octal (base 8) is deprecated - var num = 010; - -The special value `null` should be used only in the following situations: - -1. To initialize a variable that may later be assign an object value. -1. To compare against an initialized variable that may or may not have an object value. -1. To pass into a function where an object is expected. -1. To return from a function where an object is expected. - -Examples: - - // Good - var person = null; - - // Good - function getPerson() { - if (condition) { - return new Person("Nicholas"); - } else { - return null; - } - } - - // Good - var person = getPerson(); - if (person !== null){ - doSomething(); - } - - // Bad: Testing against uninitialized variable - var person; - if (person != null){ - doSomething(); - } - - // Bad: Testing to see if an argument was passed - function doSomething(arg1, arg2, arg3, arg4){ - if (arg4 != null){ - doSomethingElse(); - } - } - -Never use the special value `undefined`. To see if a variable has been defined, use the `typeof` operator: - - // Good - if (typeof variable == "undefined") { - // do something - } - - // Bad: Using undefined literal - if (variable == undefined) { - // do something - } - -## Operator Spacing - -Operators with two operands must be preceded and followed by a single space to make the expression clear. Operators include assignments and logical operators. - - // Good - var found = (values[i] === item); - - // Good - if (found && (count > 10)) { - doSomething(); - } - - // Good - for (i = 0; i < count; i++) { - process(i); - } - - // Bad: Missing spaces - var found = (values[i]===item); - - // Bad: Missing spaces - if (found&&(count>10)) { - doSomething(); - } - - // Bad: Missing spaces - for (i=0; i 10)) { - doSomething(); - } - - // Good - for (i = 0; i < count; i++) { - process(i); - } - - // Bad: Extra space after opening paren - var found = ( values[i] === item); - - // Bad: Extra space before closing paren - if (found && (count > 10) ) { - doSomething(); - } - - // Bad: Extra space around argument - for (i = 0; i < count; i++) { - process( i ); - } - -## Object Literals - -Object literals should have the following format: - -* The opening brace should be on the same line as the containing statement. -* Each property-value pair should be indented one level with the first property appearing on the next line after the opening brace. -* Each property-value pair should have an unquoted property name, followed by a colon (no space preceding it), followed by the value. -* If the value is a function, it should wrap under the property name and should have a blank line both before and after the function. -* Additional empty lines may be inserted to group related properties or otherwise improve readability. -* The closing brace should be on a separate line. - -Examples: - - // Good - var object = { - - key1: value1, - key2: value2, - - func: function() { - // do something - }, - - key3: value3 - }; - - // Bad: Improper indentation - var object = { - key1: value1, - key2: value2 - }; - - // Bad: Missing blank lines around function - var object = { - - key1: value1, - key2: value2, - func: function() { - // do something - }, - key3: value3 - }; - -When an object literal is passed to a function, the opening brace should be on the same line as if the value is a variable. All other formatting rules from above still apply. - - // Good - doSomething({ - key1: value1, - key2: value2 - }); - - // Bad: All on one line - doSomething({ key1: value1, key2: value2 }); - -## Comments - -Make frequent use of comments to aid others in understanding your code. Use comments when: - -* Code is difficult to understand. -* The code might be mistaken for an error. -* Browser-specific code is necessary but not obvious. -* Documentation generation is necessary for an object, method, or property (use appropriate documentation comments). - -### Single-Line Comments - -Single-line comments should be used to document one line of code or a group of related lines of code. A single-line comment may be used in three ways: - -1. On a separate line, describing the code beneath it. -1. At the end of a line, describing the code before it. -1. On multiple lines, to comment out sections of code. - -When on a separate line, a single-line comment should be at the same indentation level as the code it describes and be preceded by a single line. Never use multiple single-line comments on consecutive lines, use a multi-line comment instead. - - // Good - if (condition){ - - // if you made it here, then all security checks passed - allowed(); - } - - // Bad: No empty line preceding comment - if (condition){ - // if you made it here, then all security checks passed - allowed(); - } - - // Bad: Wrong indentation - if (condition){ - - // if you made it here, then all security checks passed - allowed(); - } - - // Bad: This should be a multi-line comment - // This next piece of code is quite difficult, so let me explain. - // What you want to do is determine if the condition is true - // and only then allow the user in. The condition is calculated - // from several different functions and may change during the - // lifetime of the session. - if (condition){ - // if you made it here, then all security checks passed - allowed(); - } - -For single-line comments at the end of a line, ensure there is at least one indentation level between the end of the code and the beginning of the comment: - - // Good - var result = something + somethingElse; // somethingElse will never be null - - // Bad: Not enough space between code and comment - var result = something + somethingElse;// somethingElse will never be null - -The only acceptable time to have multiple single-line comments on successive lines is to comment out large sections of code. Multi-line comments should not be used for this purpose. - - // Good - // if (condition){ - // doSomething(); - // thenDoSomethingElse(); - // } - -### Multi-Line Comments - -Multi-line comments should be used to document code that requires more explanation. Each multi-line comment should have at least three lines: - -1. The first line contains only the `/*` comment opening. No further text is allowed on this line. -1. The next line(s) have a `*` aligned with the `*` in the first line. Text is allowed on these lines. -1. The last line has the `*/` comment opening aligned with the preceding lines. No other text is allowed on this line. - -The first line of multi-comments should be indented to the same level as the code it describes. Each subsequent line should have the same indentation plus one space (for proper alignment of the `*` characters). Each multi-line comment should be preceded by one empty line. - - // Good - if (condition){ - - /* - * if you made it here, - * then all security checks passed - */ - allowed(); - } - - // Bad: No empty line preceding comment - if (condition){ - /* - * if you made it here, - * then all security checks passed - */ - allowed(); - } - - // Bad: Missing a space after asterisk - if (condition){ - - /* - *if you made it here, - *then all security checks passed - */ - allowed(); - } - - // Bad: Wrong indentation - if (condition){ - - /* - * if you made it here, - * then all security checks passed - */ - allowed(); - } - - // Bad: Don't use multi-line comments for trailing comments - var result = something + somethingElse; /*somethingElse will never be null*/ - -### Comment Annotations - -Comments may be used to annotate pieces of code with additional information. These annotations take the form of a single word followed by a colon. The acceptable annotations are: - -* `TODO` - indicates that the code is not yet complete. Information about the next steps should be included. -* `HACK` - indicates that the code is using a shortcut. Information about why the hack is being used should be included. This may also indicate that it would be nice to come up with a better way to solve the problem. -* `XXX` - indicates that the code is problematic and should be fixed as soon as possible. -* `FIXME` - indicates that the code is problematic and should be fixed soon. Less important than `XXX`. -* `REVIEW` - indicates that the code needs to be reviewed for potential changes. - -These annotations may be used with either single-line or multi-line comments and should follow the same formatting rules as the general comment type. Examples: - - // Good - // TODO: I'd like to find a way to make this faster - doSomething(); - - // Good - /* - * HACK: Have to do this for IE. I plan on revisiting in - * the future when I have more time. This probably should - * get replaced before v1.2. - */ - if (document.all) { - doSomething(); - } - - // Good - // REVIEW: Is there a better way to do this? - if (document.all) { - doSomething(); - } - - // Bad: Annotation spacing is incorrect - // TODO : I'd like to find a way to make this faster - doSomething(); - - // Bad: Comment should be at the same indentation as code - // REVIEW: Is there a better way to do this? - if (document.all) { - doSomething(); - } - - -## Variable Declarations - -All variables should be declared before they are used. Variable declarations should take place at the beginning of a function using a single `var` statement with one variable per line. All lines after the first should be indented one level so the variable names line up. Variables should be initialized when declared if applicable and the equals operator should be at a consistent indentation level. Initialized variables should come first followed by uninitialized variables. - - // Good - var count = 10, - name = "Nicholas", - found = false, - empty; - - // Bad: Improper initialization alignment - var count = 10, - name = "Nicholas", - found= false, - empty; - - // Bad: Incorrect indentation - var count = 10, - name = "Nicholas", - found = false, - empty; - - // Bad: Multiple declarations on one line - var count = 10, name = "Nicholas", - found = false, empty; - - // Bad: Uninitialized variables first - var empty, - count = 10, - name = "Nicholas", - found = false; - - // Bad: Multiple var statements - var count = 10, - name = "Nicholas"; - - var found = false, - empty; - -Always declare variables. Implied globals should not be used. - -## Function Declarations - -Functions should be declared before they are used. When a function is not a method (not attached to an object) it should be defined using function declaration format (not function expression format nor using the `Function` constructor). There should be no space between the function name and the opening parentheses. There should be one space between the closing parentheses and the right brace. The right brace should be on the same line as the `function` keyword. There should be no space after the opening parentheses or before the closing parentheses. Named arguments should have a space after the comma but not before it. The function body should be indented one level. - - // Good - function doSomething(arg1, arg2) { - return arg1 + arg2; - } - - // Bad: Improper spacing of first line - function doSomething (arg1, arg2){ - return arg1 + arg2; - } - - // Bad: Function expression - var doSomething = function(arg1, arg2) { - return arg1 + arg2; - }; - - // Bad: Left brace on wrong line - function doSomething(arg1, arg2) - { - return arg1 + arg2; - } - - // Bad: Using Function constructor - var doSomething = new Function("arg1", "arg2", "return arg1 + arg2"); - -Functions declared inside of other functions should be declared immediately after the `var` statement. - - // Good - function outer() { - - var count = 10, - name = "Nicholas", - found = false, - empty; - - function inner() { - // code - } - - // code that uses inner() - } - - // Bad: Inner function declared before variables - function outer() { - - function inner() { - // code - } - - var count = 10, - name = "Nicholas", - found = false, - empty; - - // code that uses inner() - } - -Anonymous functions may be used for assignment of object methods or as arguments to other functions. There should be no space between the `function` keyword and the opening parentheses. - - // Good - object.method = function() { - // code - }; - - // Bad: Incorrect spacing - object.method = function () { - // code - }; - -Immediately-invoked functions should surround the entire function call with parentheses. - - // Good - var value = (function() { - - // function body - - return { - message: "Hi" - } - }()); - - // Bad: No parentheses around function call - var value = function() { - - // function body - - return { - message: "Hi" - } - }(); - - // Bad: Improper parentheses placement - var value = (function() { - - // function body - - return { - message: "Hi" - } - })(); - -## Naming - -Care should be taken to name variables and functions properly. Names should be limited to alphanumeric characters and, in some cases, the underscore character. Do not use the dollar sign (`$`) or back slash (`\`) characters in any names. - -Variable names should be formatted in camel case with the first letter being lowercase and the first letter of each subsequent word being uppercase. The first word of a variable name should be a noun (not a verb) to avoid confusion with functions. Do not use underscore for variable names. - - // Good - var accountNumber = "8401-1"; - - // Bad: Begins with uppercase letter - var AccountNumber = "8401-1"; - - // Bad: Begins with verb - var getAccountNumber = "8401-1"; - - // Bad: Uses underscore - var account_number = "8401-1"; - -Function names should also be formatted using camel case. The first word of a function name should be a verb (not a noun) to avoid confusion with variables. Do not use underscore for function names. - - // Good - function doSomething() { - // code - } - - // Bad: Begins with uppercase letter - function DoSomething() { - // code - } - - // Bad: Begins with noun - function car() { - // code - } - - // Bad: Uses underscores - function do_something() { - // code - } - -Constructor functions, those functions used with the `new` operator to create new objects, should be formatted in camel case but must begin with an uppercase letter. Constructor function names should begin with a non-verb because `new` is the action of creating an object instance. - - // Good - function MyObject() { - // code - } - - // Bad: Begins with lowercase letter - function myObject() { - // code - } - - // Bad: Uses underscores - function My_Object() { - // code - } - - // Bad: Begins with verb - function getMyObject() { - // code - } - -Variables that act as constants (values that won't be changed) should be formatted using all uppercase letters with words separated by a single underscore. - - // Good - var TOTAL_COUNT = 10; - - // Bad: Camel case - var totalCount = 10; - - // Bad: Mixed case - var total_COUNT = 10; - -Object properties follow the same naming conventions as variables. Object methods follow the same naming conventions as functions. If a property or method is meant to be private, then it should be prefixed with an underscore character. - - // Good - var object = { - _count: 10, - - _getCount: function () { - return this._count; - } - }; - -## Strict Mode - -Strict mode should be used in all modules, specified below the file overview comment and above everything else: - - // Bad: Strict mode in functions - function doSomething() { - "use strict"; - - // code - } - - // Bad: Strict mode in global scope and redundant strict mode directive in function - "use strict"; // This one is good - - function doSomething() { - "use strict"; // This one is bad - - // code - } - - // Good: Global strict mode - "use strict"; - - function doSomething() { - // no "use strict" here - - // code - } - -## Assignments - -When assigning a value to a variable, use parentheses around a right-side expression that contains a comparison. - - // Good - var flag = (i < count); - - // Bad: Missing parentheses - var flag = i < count; - -## Equality Operators - -Use `===` and `!==` instead of `==` and `!=`. This avoids type coercion errors. - - // Good - var same = (a === b); - - // Bad: Using == - var same = (a == b); - -## Ternary Operator - -The ternary operator should be used only for assigning values conditionally and never as a shortcut for an `if` statement. - - // Good - var value = condition ? value1 : value2; - - // Bad: no assignment, should be an if statement - condition ? doSomething() : doSomethingElse(); - -## Statements - -### Simple Statements - -Each line should contain at most one statement. All simple statements should end with a semicolon (`;`). - - // Good - count++; - a = b; - - // Bad: Multiple statements on one line - count++; a = b; - -### return Statement - -A return statement with a value should not use parentheses unless they make the return value more obvious in some way. Example: - - return; - - return collection.size(); - - return (size > 0 ? size : defaultSize); - -### Compound Statements - -Compound statements are lists of statements enclosed inside of braces. - -* The enclosed statements should be indented one more level than the compound statement. -* The opening brace should be at the end of the line that begins the compound statement; the closing brace should begin a line and be indented to the beginning of the compound statement. -* Braces are used around all statements, even single statements, when they are part of a control structure, such as a `if` or `for` statement. This makes it easier to add statements without accidentally introducing bugs due to forgetting to add braces. -* The statement beginning keyword, such as `if`, should be followed by one space and the opening brace should be preceded by a space. - -### if Statement - -The `if` class of statements should have the following form: - - if (condition) { - statements - } - - if (condition) { - statements - } else { - statements - } - - if (condition) { - statements - } else if (condition) { - statements - } else { - statements - } - -It is never permissible to omit the braces in any part of an `if` statement. - - // Good - if (condition) { - doSomething(); - } - - // Bad: Improper spacing - if(condition){ - doSomething(); - } - - // Bad: Missing braces - if (condition) - doSomething(); - - // Bad: All on one line - if (condition) { doSomething(); } - - // Bad: All on one line without braces - if (condition) doSomething(); - -### for Statement - -The `for` class of statements should have the following form: - - for (initialization; condition; update) { - statements - } - - for (variable in object) { - statements - } - -Variables should not be declared in the initialization section of a `for` statement. - - // Good - var i, - len; - - for (i=0, len=10; i < len; i++) { - // code - } - - // Bad: Variables declared during initialization - for (var i=0, len=10; i < len; i++) { - // code - } - - // Bad: Variables declared during initialization - for (var prop in object) { - // code - } - -When using a `for-in` statement, double-check whether or not you need to use `hasOwnProperty()` to filter out object members. - -### while Statement - -The `while` class of statements should have the following form: - - while (condition) { - statements - } - -### do Statement - -The `do` class of statements should have the following form: - - do { - statements - } while (condition); - -Note the use of a semicolon as the final part of this statement. There should be a space before and after the `while` keyword. - -### switch Statement - -The `switch` class of statements should have the following form: - - switch (expression) { - case expression: - statements - - default: - statements - } - -Each `case` is indented one level under the `switch`. Each `case` after the first, including `default`, should be preceded by a single empty line. - -Each group of statements (except the default) should end with `break`, `return`, `throw`, or a comment indicating fall through. - - // Good - switch (value) { - case 1: - /* falls through */ - - case 2: - doSomething(); - break; - - case 3: - return true; - - default: - throw new Error("This shouldn't happen.); - } - -If a `switch` doesn't have a `default` case, then it should be indicated with a comment. - - // Good - switch (value) { - case 1: - /*falls through*/ - - case 2: - doSomething(); - break; - - case 3: - return true; - - // no default - } - -### try Statement - -The `try` class of statements should have the following form: - - try { - statements - } catch (variable) { - statements - } - - try { - statements - } catch (variable) { - statements - } finally { - statements - } - -## Whitespace - -Blank lines improve readability by setting off sections of code that are logically related. - -Two blank lines should always be used in the following circumstances: - -* Between sections of a source file -* Between class and interface definitions - -One blank line should always be used in the following circumstances: - -* Between methods -* Between the local variables in a method and its first statement -* Before a multi-line or single-line comment -* Between logical sections inside a method to improve readability - -Blank spaces should be used in the following circumstances: - -* A keyword followed by a parenthesis should be separated by a space. -* A blank space should appear after commas in argument lists. -* All binary operators except dot (`.`) should be separated from their operands by spaces. Blank spaces should never separate unary operators such as unary minus, increment (`++`), and decrement (`--`) from their operands. -* The expressions in a `for` statement should be separated by blank spaces. Blank spaces should only be used after semicolons, not before. - -## Things to Avoid - -* Never use the primitive wrapper types, such as `String`, to create new objects. -* Never use `eval()`. -* Never use the `with` statement. This statement isn't available in strict mode and likely won't be available in future ECMAScript editions. +The rationales for the specific rules in use can be found by looking to the +project documentation for any given rule. If the rule is one of our own, see +our own [rule documentation](https://eslint.org/docs/rules/) and otherwise, see +the documentation of the plugin in which the rule can be found. diff --git a/eslint/docs/developer-guide/code-path-analysis.md b/eslint/docs/developer-guide/code-path-analysis.md index cd3fc29..f22eb8f 100644 --- a/eslint/docs/developer-guide/code-path-analysis.md +++ b/eslint/docs/developer-guide/code-path-analysis.md @@ -27,6 +27,7 @@ This has references of both the initial segment and the final segments of a code `CodePath` has the following properties: * `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each code path. +* `origin` (`string`) - The reason that the code path was started. May be `"program"`, `"function"`, `"class-field-initializer"`, or `"class-static-block"`. * `initialSegment` (`CodePathSegment`) - The initial segment of this code path. * `finalSegments` (`CodePathSegment[]`) - The final segments which includes both returned and thrown. * `returnedSegments` (`CodePathSegment[]`) - The final segments which includes only returned. @@ -225,9 +226,9 @@ module.exports = function(context) { ``` See Also: -[no-unreachable](https://github.com/eslint/eslint/blob/master/lib/rules/no-unreachable.js), -[no-fallthrough](https://github.com/eslint/eslint/blob/master/lib/rules/no-fallthrough.js), -[consistent-return](https://github.com/eslint/eslint/blob/master/lib/rules/consistent-return.js) +[no-unreachable](https://github.com/eslint/eslint/blob/HEAD/lib/rules/no-unreachable.js), +[no-fallthrough](https://github.com/eslint/eslint/blob/HEAD/lib/rules/no-fallthrough.js), +[consistent-return](https://github.com/eslint/eslint/blob/HEAD/lib/rules/consistent-return.js) ### To check state of a code path @@ -323,8 +324,8 @@ module.exports = function(context) { ``` See Also: -[constructor-super](https://github.com/eslint/eslint/blob/master/lib/rules/constructor-super.js), -[no-this-before-super](https://github.com/eslint/eslint/blob/master/lib/rules/no-this-before-super.js) +[constructor-super](https://github.com/eslint/eslint/blob/HEAD/lib/rules/constructor-super.js), +[no-this-before-super](https://github.com/eslint/eslint/blob/HEAD/lib/rules/no-this-before-super.js) ## Code Path Examples diff --git a/eslint/docs/developer-guide/code-path-analysis/README.md b/eslint/docs/developer-guide/code-path-analysis/README.md index 1c84b2e..2b6fd74 100644 --- a/eslint/docs/developer-guide/code-path-analysis/README.md +++ b/eslint/docs/developer-guide/code-path-analysis/README.md @@ -1,549 +1 @@ -# Code Path Analysis Details - -ESLint's rules can use code paths. -The code path is execution routes of programs. -It forks/joins at such as `if` statements. - -```js -if (a && b) { - foo(); -} -bar(); -``` - -![Code Path Example](./helo.svg) - -## Objects - -Program is expressed with several code paths. -A code path is expressed with objects of two kinds: `CodePath` and `CodePathSegment`. - -### `CodePath` - -`CodePath` expresses whole of one code path. -This object exists for each function and the global. -This has references of both the initial segment and the final segments of a code path. - -`CodePath` has the following properties: - -* `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each code path. -* `initialSegment` (`CodePathSegment`) - The initial segment of this code path. -* `finalSegments` (`CodePathSegment[]`) - The final segments which includes both returned and thrown. -* `returnedSegments` (`CodePathSegment[]`) - The final segments which includes only returned. -* `thrownSegments` (`CodePathSegment[]`) - The final segments which includes only thrown. -* `currentSegments` (`CodePathSegment[]`) - Segments of the current position. -* `upper` (`CodePath|null`) - The code path of the upper function/global scope. -* `childCodePaths` (`CodePath[]`) - Code paths of functions this code path contains. - -### `CodePathSegment` - -`CodePathSegment` is a part of a code path. -A code path is expressed with plural `CodePathSegment` objects, it's similar to doubly linked list. -Difference from doubly linked list is what there are forking and merging (the next/prev are plural). - -`CodePathSegment` has the following properties: - -* `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each segment. -* `nextSegments` (`CodePathSegment[]`) - The next segments. If forking, there are two or more. If final, there is nothing. -* `prevSegments` (`CodePathSegment[]`) - The previous segments. If merging, there are two or more. If initial, there is nothing. -* `reachable` (`boolean`) - A flag which shows whether or not it's reachable. This becomes `false` when preceded by `return`, `throw`, `break`, or `continue`. - -## Events - -There are five events related to code paths, and you can define event handlers in rules. - -```js -module.exports = function(context) { - return { - /** - * This is called at the start of analyzing a code path. - * In this time, the code path object has only the initial segment. - * - * @param {CodePath} codePath - The new code path. - * @param {ASTNode} node - The current node. - * @returns {void} - */ - "onCodePathStart": function(codePath, node) { - // do something with codePath - }, - - /** - * This is called at the end of analyzing a code path. - * In this time, the code path object is complete. - * - * @param {CodePath} codePath - The completed code path. - * @param {ASTNode} node - The current node. - * @returns {void} - */ - "onCodePathEnd": function(codePath, node) { - // do something with codePath - }, - - /** - * This is called when a code path segment was created. - * It meant the code path is forked or merged. - * In this time, the segment has the previous segments and has been - * judged reachable or not. - * - * @param {CodePathSegment} segment - The new code path segment. - * @param {ASTNode} node - The current node. - * @returns {void} - */ - "onCodePathSegmentStart": function(segment, node) { - // do something with segment - }, - - /** - * This is called when a code path segment was leaved. - * In this time, the segment does not have the next segments yet. - * - * @param {CodePathSegment} segment - The leaved code path segment. - * @param {ASTNode} node - The current node. - * @returns {void} - */ - "onCodePathSegmentEnd": function(segment, node) { - // do something with segment - }, - - /** - * This is called when a code path segment was looped. - * Usually segments have each previous segments when created, - * but when looped, a segment is added as a new previous segment into a - * existing segment. - * - * @param {CodePathSegment} fromSegment - A code path segment of source. - * @param {CodePathSegment} toSegment - A code path segment of destination. - * @param {ASTNode} node - The current node. - * @returns {void} - */ - "onCodePathSegmentLoop": function(fromSegment, toSegment, node) { - // do something with segment - } - }; -}; -``` - -### About `onCodePathSegmentLoop` - -This event is always fired when the next segment has existed already. -That timing is the end of loops mainly. - -For Example 1: - -```js -while (a) { - a = foo(); -} -bar(); -``` - -1. First, the analysis advances to the end of loop. - - ![Loop Event's Example 1](./loop-event-example-while-1.svg) - -2. Second, it creates the looping path. - At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired. - It fires `onCodePathSegmentLoop` instead. - - ![Loop Event's Example 2](./loop-event-example-while-2.svg) - -3. Last, it advances to the end. - - ![Loop Event's Example 3](./loop-event-example-while-3.svg) - -For example 2: - -```js -for (let i = 0; i < 10; ++i) { - foo(i); -} -bar(); -``` - -1. `for` statements are more complex. - First, the analysis advances to `ForStatement.update`. - The `update` segment is hovered at first. - - ![Loop Event's Example 1](./loop-event-example-for-1.svg) - -2. Second, it advances to `ForStatement.body`. - Of course the `body` segment is preceded by the `test` segment. - It keeps the `update` segment hovering. - - ![Loop Event's Example 2](./loop-event-example-for-2.svg) - -3. Third, it creates the looping path from `body` segment to `update` segment. - At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired. - It fires `onCodePathSegmentLoop` instead. - - ![Loop Event's Example 3](./loop-event-example-for-3.svg) - -4. Fourth, also it creates the looping path from `update` segment to `test` segment. - At this time, the next segment has existed already, so the `onCodePathSegmentStart` event is not fired. - It fires `onCodePathSegmentLoop` instead. - - ![Loop Event's Example 4](./loop-event-example-for-4.svg) - -5. Last, it advances to the end. - - ![Loop Event's Example 5](./loop-event-example-for-5.svg) - - - -## Usage Examples - -### To check whether or not this is reachable - -```js -function isReachable(segment) { - return segment.reachable; -} - -module.exports = function(context) { - var codePathStack = []; - - return { - // Stores CodePath objects. - "onCodePathStart": function(codePath) { - codePathStack.push(codePath); - }, - "onCodePathEnd": function(codePath) { - codePathStack.pop(); - }, - - // Checks reachable or not. - "ExpressionStatement": function(node) { - var codePath = codePathStack[codePathStack.length - 1]; - - // Checks the current code path segments. - if (!codePath.currentSegments.some(isReachable)) { - context.report({message: "Unreachable!", node: node}); - } - } - }; -}; -``` - -See Also: -[no-unreachable](https://github.com/eslint/eslint/blob/master/lib/rules/no-unreachable.js), -[no-fallthrough](https://github.com/eslint/eslint/blob/master/lib/rules/no-fallthrough.js), -[consistent-return](https://github.com/eslint/eslint/blob/master/lib/rules/consistent-return.js) - -### To check state of a code path - -This example is checking whether or not the parameter `cb` is called in every path. -Instances of `CodePath` and `CodePathSegment` are shared to every rule. -So a rule must not modify those instances. -Please use a map of information instead. - -```js -function hasCb(node, context) { - if (node.type.indexOf("Function") !== -1) { - return context.getDeclaredVariables(node).some(function(v) { - return v.type === "Parameter" && v.name === "cb"; - }); - } - return false; -} - -function isCbCalled(info) { - return info.cbCalled; -} - -module.exports = function(context) { - var funcInfoStack = []; - var segmentInfoMap = Object.create(null); - - return { - // Checks `cb`. - "onCodePathStart": function(codePath, node) { - funcInfoStack.push({ - codePath: codePath, - hasCb: hasCb(node, context) - }); - }, - "onCodePathEnd": function(codePath, node) { - funcInfoStack.pop(); - - // Checks `cb` was called in every paths. - var cbCalled = codePath.finalSegments.every(function(segment) { - var info = segmentInfoMap[segment.id]; - return info.cbCalled; - }); - - if (!cbCalled) { - context.report({ - message: "`cb` should be called in every path.", - node: node - }); - } - }, - - // Manages state of code paths. - "onCodePathSegmentStart": function(segment) { - var funcInfo = funcInfoStack[funcInfoStack - 1]; - - // Ignores if `cb` doesn't exist. - if (!funcInfo.hasCb) { - return; - } - - // Initialize state of this path. - var info = segmentInfoMap[segment.id] = { - cbCalled: false - }; - - // If there are the previous paths, merges state. - // Checks `cb` was called in every previous path. - if (segment.prevSegments.length > 0) { - info.cbCalled = segment.prevSegments.every(isCbCalled); - } - }, - - // Checks reachable or not. - "CallExpression": function(node) { - var funcInfo = funcInfoStack[funcInfoStack - 1]; - - // Ignores if `cb` doesn't exist. - if (!funcInfo.hasCb) { - return; - } - - // Sets marks that `cb` was called. - var callee = node.callee; - if (callee.type === "Identifier" && callee.name === "cb") { - funcInfo.codePath.currentSegments.forEach(function(segment) { - var info = segmentInfoMap[segment.id]; - info.cbCalled = true; - }); - } - } - }; -}; -``` - -See Also: -[constructor-super](https://github.com/eslint/eslint/blob/master/lib/rules/constructor-super.js), -[no-this-before-super](https://github.com/eslint/eslint/blob/master/lib/rules/no-this-before-super.js) - -## Code Path Examples - -### Hello World - -```js -console.log("Hello world!"); -``` - -![Hello World](./example-hello-world.svg) - -### `IfStatement` - -```js -if (a) { - foo(); -} else { - bar(); -} -``` - -![`IfStatement`](./example-ifstatement.svg) - -### `IfStatement` (chain) - -```js -if (a) { - foo(); -} else if (b) { - bar(); -} else if (c) { - hoge(); -} -``` - -![`IfStatement` (chain)](./example-ifstatement-chain.svg) - -### `SwitchStatement` - -```js -switch (a) { - case 0: - foo(); - break; - - case 1: - case 2: - bar(); - // fallthrough - - case 3: - hoge(); - break; -} -``` - -![`SwitchStatement`](./example-switchstatement.svg) - -### `SwitchStatement` (has `default`) - -```js -switch (a) { - case 0: - foo(); - break; - - case 1: - case 2: - bar(); - // fallthrough - - case 3: - hoge(); - break; - - default: - fuga(); - break; -} -``` - -![`SwitchStatement` (has `default`)](./example-switchstatement-has-default.svg) - -### `TryStatement` (try-catch) - -```js -try { - foo(); - if (a) { - throw new Error(); - } - bar(); -} catch (err) { - hoge(err); -} -last(); -``` - -It creates the paths from `try` block to `catch` block at: - -* `throw` statements. -* The first throwable node (e.g. a function call) in the `try` block. -* The end of the `try` block. - -![`TryStatement` (try-catch)](./example-trystatement-try-catch.svg) - -### `TryStatement` (try-finally) - -```js -try { - foo(); - bar(); -} finally { - fuga(); -} -last(); -``` - -If there is not `catch` block, `finally` block has two current segments. -At this time, `CodePath.currentSegments.length` is `2`. -One is the normal path, and another is the leaving path (`throw` or `return`). - -![`TryStatement` (try-finally)](./example-trystatement-try-finally.svg) - -### `TryStatement` (try-catch-finally) - -```js -try { - foo(); - bar(); -} catch (err) { - hoge(err); -} finally { - fuga(); -} -last(); -``` - -![`TryStatement` (try-catch-finally)](./example-trystatement-try-catch-finally.svg) - -### `WhileStatement` - -```js -while (a) { - foo(); - if (b) { - continue; - } - bar(); -} -``` - -![`WhileStatement`](./example-whilestatement.svg) - -### `DoWhileStatement` - -```js -do { - foo(); - bar(); -} while (a); -``` - -![`DoWhileStatement`](./example-dowhilestatement.svg) - -### `ForStatement` - -```js -for (let i = 0; i < 10; ++i) { - foo(); - if (b) { - break; - } - bar(); -} -``` - -![`ForStatement`](./example-forstatement.svg) - -### `ForStatement` (for ever) - -```js -for (;;) { - foo(); -} -bar(); -``` - -![`ForStatement` (for ever)](./example-forstatement-for-ever.svg) - -### `ForInStatement` - -```js -for (let key in obj) { - foo(key); -} -``` - -![`ForInStatement`](./example-forinstatement.svg) - -### When there is a function - -```js -function foo(a) { - if (a) { - return; - } - bar(); -} - -foo(false); -``` - -It creates two code paths. - -* The global's - - ![When there is a function](./example-when-there-is-a-function-g.svg) - -* The function's - - ![When there is a function](./example-when-there-is-a-function-f.svg) +[Code Path Analysis Details](../code-path-analysis.md) diff --git a/eslint/docs/developer-guide/contributing/README.md b/eslint/docs/developer-guide/contributing/README.md index e341083..25a8894 100644 --- a/eslint/docs/developer-guide/contributing/README.md +++ b/eslint/docs/developer-guide/contributing/README.md @@ -10,7 +10,7 @@ ESLint welcomes contributions from everyone and adheres to the [OpenJS Foundatio ## [Signing the CLA](https://openjsf.org/about/the-openjs-foundation-cla/) -In order to submit code or documentation to an ESLint project, you will need to electronically sign our [Contributor License Agreement](https://cla.js.foundation/eslint/eslint). The CLA is you giving us permission to use your contribution. +In order to submit code or documentation to an ESLint project, you will need to electronically sign our [Contributor License Agreement](https://github.com/openjs-foundation/easycla). The CLA is the commonly used Apache-style template, and is you giving us permission to use your contribution. You only need to sign the CLA once for any OpenJS Foundation projects that use EasyCLA. ## [Bug Reporting](reporting-bugs) diff --git a/eslint/docs/developer-guide/contributing/pull-requests.md b/eslint/docs/developer-guide/contributing/pull-requests.md index ef4169e..dde1c31 100644 --- a/eslint/docs/developer-guide/contributing/pull-requests.md +++ b/eslint/docs/developer-guide/contributing/pull-requests.md @@ -47,37 +47,42 @@ $ git add -A $ git commit ``` -Our commit message format is as follows: +All ESLint projects follow [Conventional Commits](https://www.conventionalcommits.org/) for our commit messages. Here's an example commit message: ``` -Tag: Short description (fixes #1234) +tag: Short description of what you did Longer description here if necessary + +Fixes #1234 ``` The first line of the commit message (the summary) must have a specific format. This format is checked by our build tools. -The `Tag` is one of the following: +The `tag` is one of the following: -* `Fix` - for a bug fix. -* `Update` - either for a backwards-compatible enhancement or for a rule change that adds reported problems. -* `New` - implemented a new feature. -* `Breaking` - for a backwards-incompatible enhancement or feature. -* `Docs` - changes to documentation only. -* `Build` - changes to build process only. -* `Upgrade` - for a dependency upgrade. -* `Chore` - for refactoring, adding tests, etc. (anything that isn't user-facing). +* `fix` - for a bug fix. +* `feat` - either for a backwards-compatible enhancement or for a rule change that adds reported problems. +* `fix!` - for a backwards-incompatible bug fix. +* `feat!` - for a backwards-incompatible enhancement or feature. +* `docs` - changes to documentation only. +* `chore` - for changes that aren't user-facing. +* `build` - changes to build process only. +* `refactor` - a change that doesn't affect APIs or user experience. +* `test` - just changes to test files. +* `ci` - changes to our CI configuration files and scripts. +* `perf` - a code change that improves performance. Use the [labels of the issue you are working on](working-on-issues.md#issue-labels) to determine the best tag. -The message summary should be a one-sentence description of the change, and it must be 72 characters in length or shorter. If the pull request addresses an issue, then the issue number should be mentioned at the end. If the commit doesn't completely fix the issue, then use `(refs #1234)` instead of `(fixes #1234)`. +The message summary should be a one-sentence description of the change, and it must be 72 characters in length or shorter. If the pull request addresses an issue, then the issue number should be mentioned in the body of the commit message in the format `Fixes #1234`. If the commit doesn't completely fix the issue, then use `Refs #1234` instead of `Fixes #1234`. Here are some good commit message summary examples: ``` -Build: Update Travis to only test Node 0.10 (refs #734) -Fix: Semi rule incorrectly flagging extra semicolon (fixes #840) -Upgrade: Esprima to 1.2, switch to using comment attachment (fixes #730) +build: Update Travis to only test Node 0.10 +fix: Semi rule incorrectly flagging extra semicolon +chore: Upgrade Esprima to 1.2, switch to using comment attachment ``` The commit message format is important because these messages are used to create a changelog for each release. The tag and issue number help to create more consistent and useful changelogs. @@ -88,7 +93,7 @@ Before you send the pull request, be sure to rebase onto the upstream source. Th ``` git fetch upstream -git rebase upstream/master +git rebase upstream/main ``` ### Step 4: Run the tests @@ -164,7 +169,9 @@ $ git commit $ git push origin issue1234 ``` -When updating the code, it's usually better to add additional commits to your branch rather than amending the original commit, because reviewers can easily tell which changes were made in response to a particular review. When we merge pull requests, we will squash all the commits from your branch into a single commit on the `master` branch. +When updating the code, it's usually better to add additional commits to your branch rather than amending the original commit, because reviewers can easily tell which changes were made in response to a particular review. When we merge pull requests, we will squash all the commits from your branch into a single commit on the `main` branch. + +The commit messages in subsequent commits do not need to be in any specific format because these commits do not show up in the changelog. ### Rebasing @@ -172,7 +179,7 @@ If your code is out-of-date, we might ask you to rebase. That means we want you ``` $ git fetch upstream -$ git rebase upstream/master +$ git rebase upstream/main ``` You might find that there are merge conflicts when you attempt to rebase. Please [resolve the conflicts](https://help.github.com/articles/resolving-merge-conflicts-after-a-git-rebase/) and then do a forced push to your branch: diff --git a/eslint/docs/developer-guide/development-environment.md b/eslint/docs/developer-guide/development-environment.md index 8433bd0..7981c66 100644 --- a/eslint/docs/developer-guide/development-environment.md +++ b/eslint/docs/developer-guide/development-environment.md @@ -81,12 +81,8 @@ Be sure to run this after making changes and before sending a pull request with #### npm run lint -Runs just the JavaScript and JSON linting on the repository +Runs just the JavaScript and JSON linting on the repository. #### npm run webpack -Generates `build/eslint.js`, a version of ESLint for use in the browser - -#### npm run docs - -Generates JSDoc documentation and places it into `/jsdoc`. +Generates `build/eslint.js`, a version of ESLint for use in the browser. diff --git a/eslint/docs/developer-guide/nodejs-api.md b/eslint/docs/developer-guide/nodejs-api.md index f1e2158..378b9c1 100644 --- a/eslint/docs/developer-guide/nodejs-api.md +++ b/eslint/docs/developer-guide/nodejs-api.md @@ -10,6 +10,7 @@ While ESLint is designed to be run on the command line, it's possible to use ESL * [constructor()][eslint-constructor] * [lintFiles()][eslint-lintfiles] * [lintText()][eslint-linttext] + * [getRulesMetaForResults()][eslint-getrulesmetaforresults] * [calculateConfigForFile()][eslint-calculateconfigforfile] * [isPathIgnored()][eslint-ispathignored] * [loadFormatter()][eslint-loadformatter] @@ -30,11 +31,8 @@ While ESLint is designed to be run on the command line, it's possible to use ESL * [getRules()](#lintergetrules) * [defineParser()](#linterdefineparser) * [version](#linterversionlinterversion) -* [linter (deprecated)](#linter-1) -* [CLIEngine (deprecated)](#cliengine) * [RuleTester](#ruletester) * [Customizing RuleTester](#customizing-ruletester) -* [Deprecated APIs](#deprecated-apis) --- @@ -147,7 +145,7 @@ The `ESLint` constructor takes an `options` object. If you omit the `options` ob * `options.fix` (`boolean | (message: LintMessage) => boolean`)
Default is `false`. If `true` is present, the [`eslint.lintFiles()`][eslint-lintfiles] and [`eslint.lintText()`][eslint-linttext] methods work in autofix mode. If a predicate function is present, the methods pass each lint message to the function, then use only the lint messages for which the function returned `true`. -* `options.fixTypes` (`("problem" | "suggestion" | "layout")[] | null`)
+* `options.fixTypes` (`("directive" | "problem" | "suggestion" | "layout")[] | null`)
Default is `null`. The types of the rules that the [`eslint.lintFiles()`][eslint-lintfiles] and [`eslint.lintText()`][eslint-linttext] methods use for autofix. ##### Cache-related @@ -205,6 +203,25 @@ The second parameter `options` is omittable. * (`Promise`)
The promise that will be fulfilled with an array of [LintResult] objects. This is an array (despite there being only one lint result) in order to keep the interfaces between this and the [`eslint.lintFiles()`][eslint-lintfiles] method similar. +### ◆ eslint.getRulesMetaForResults(results) + +```js +const results = await eslint.lintFiles(patterns); +const rulesMeta = eslint.getRulesMetaForResults(results); +``` + +This method returns an object containing meta information for each rule that triggered a lint error in the given `results`. + +#### Parameters + +* `results` (`LintResult[]`)
+ An array of [LintResult] objects returned from a call to `ESLint#lintFiles()` or `ESLint#lintText()`. + +#### Return Value + +* (`Object`)
+ An object whose property names are the rule IDs from the `results` and whose property values are the rule's meta information (if available). + ### ◆ eslint.calculateConfigForFile(filePath) ```js @@ -335,7 +352,9 @@ The `LintResult` value is the information of the linting result of each file. Th * `fixableWarningCount` (`number`)
The number of warnings that can be fixed automatically by the `fix` constructor option. * `errorCount` (`number`)
- The number of errors. This includes fixable errors. + The number of errors. This includes fixable errors and fatal errors. +* `fatalErrorCount` (`number`)
+ The number of fatal errors. * `warningCount` (`number`)
The number of warnings. This includes fixable warnings. * `output` (`string | undefined`)
@@ -357,9 +376,9 @@ The `LintMessage` value is the information of each linting error. The `messages` `true` if this is a fatal error unrelated to a rule, like a parsing error. * `message` (`string`)
The error message. -* `line` (`number`)
+* `line` (`number | undefined`)
The 1-based line number of the begin point of this message. -* `column` (`number`)
+* `column` (`number | undefined`)
The 1-based column number of the begin point of this message. * `endLine` (`number | undefined`)
The 1-based line number of the end point of this message. This property is undefined if this message is not a range. @@ -439,10 +458,11 @@ const codeLines = SourceCode.splitLines(code); ## Linter -The `Linter` object does the actual evaluation of the JavaScript code. It doesn't do any filesystem operations, it simply parses and reports on the code. In particular, the `Linter` object does not process configuration objects or files. +The `Linter` object does the actual evaluation of the JavaScript code. It doesn't do any filesystem operations, it simply parses and reports on the code. In particular, the `Linter` object does not process configuration objects or files. Unless you are working in the browser, you probably want to use the [ESLint class](#eslint-class) class instead. + The `Linter` is a constructor, and you can create a new instance by passing in the options you want to use. The available options are: -* `cwd` - Path to a directory that should be considered as the current working directory. It is accessible to rules by calling `context.getCwd()` (see [The Context Object](./working-with-rules.md#The-Context-Object)). If `cwd` is `undefined`, it will be normalized to `process.cwd()` if the global `process` object is defined (for example, in the Node.js runtime) , or `undefined` otherwise. +* `cwd` - Path to a directory that should be considered as the current working directory. It is accessible to rules by calling `context.getCwd()` (see [The Context Object](./working-with-rules.md#the-context-object)). If `cwd` is `undefined`, it will be normalized to `process.cwd()` if the global `process` object is defined (for example, in the Node.js runtime) , or `undefined` otherwise. For example: @@ -460,8 +480,8 @@ Those run on `linter2` will get `process.cwd()` if the global `process` object i The most important method on `Linter` is `verify()`, which initiates linting of the given text. This method accepts three arguments: * `code` - the source code to lint (a string or instance of `SourceCode`). -* `config` - a configuration object that has been processed and normalized by CLIEngine using eslintrc files and/or other configuration arguments. - * **Note**: If you want to lint text and have your configuration be read and processed, use CLIEngine's [`executeOnFiles`](#cliengineexecuteonfiles) or [`executeOnText`](#cliengineexecuteontext) instead. +* `config` - a configuration object that has been processed and normalized by `ESLint` using eslintrc files and/or other configuration arguments. + * **Note**: If you want to lint text and have your configuration be read and processed, use [`ESLint#lintFiles()`][eslint-lintfiles] or [`ESLint#lintText()`][eslint-linttext] instead. * `options` - (optional) Additional options for this run. * `filename` - (optional) the filename to associate with the source code. * `preprocess` - (optional) A function that [Processors in Plugins](/docs/developer-guide/working-with-plugins.md#processors-in-plugins) documentation describes as the `preprocess` method. @@ -679,509 +699,6 @@ const Linter = require("eslint").Linter; Linter.version; // => '4.5.0' ``` -## linter - -The `eslint.linter` object (deprecated) is an instance of the `Linter` class as defined [above](#linter). `eslint.linter` exists for backwards compatibility, but we do not recommend using it because any mutations to it are shared among every module that uses `eslint`. Instead, please create your own instance of `eslint.Linter`. - -```js -const linter = require("eslint").linter; - -const messages = linter.verify("var foo;", { - rules: { - semi: 2 - } -}, { filename: "foo.js" }); -``` - -Note: This API is deprecated as of 4.0.0. - ---- - -## CLIEngine - -⚠️ The `CLIEngine` class has been deprecated in favor of the `ESLint` class as of v7.0.0. - -The primary Node.js API is `CLIEngine`, which is the underlying utility that runs the ESLint command line interface. This object will read the filesystem for configuration and file information but will not output any results. Instead, it allows you direct access to the important information so you can deal with the output yourself. - -You can get a reference to the `CLIEngine` by doing the following: - -```js -const CLIEngine = require("eslint").CLIEngine; -``` - -The `CLIEngine` is a constructor, and you can create a new instance by passing in the options you want to use. The available options are: - -* `allowInlineConfig` - Set to `false` to disable the use of configuration comments (such as `/*eslint-disable*/`). Corresponds to `--no-inline-config`. -* `baseConfig` - Can optionally be set to a config object that has the same schema as `.eslintrc.*`. This will used as a default config, and will be merged with any configuration defined in `.eslintrc.*` files, with the `.eslintrc.*` files having precedence. -* `cache` - Operate only on changed files (default: `false`). Corresponds to `--cache`. -* `cacheFile` - Name of the file where the cache will be stored (default: `.eslintcache`). Corresponds to `--cache-file`. Deprecated: use `cacheLocation` instead. -* `cacheLocation` - Name of the file or directory where the cache will be stored (default: `.eslintcache`). Corresponds to `--cache-location`. -* `configFile` - The configuration file to use (default: null). If `useEslintrc` is true or not specified, this configuration will be merged with any configuration defined in `.eslintrc.*` files, with options in this configuration having precedence. Corresponds to `-c`. -* `cwd` - Path to a directory that should be considered as the current working directory. -* `envs` - An array of environments to load (default: empty array). Corresponds to `--env`. Note: This differs from `.eslintrc.*` / `baseConfig`, where instead the option is called `env` and is an object. -* `errorOnUnmatchedPattern` - Set to `false` to prevent errors when pattern is unmatched. Corresponds to `--no-error-on-unmatched-pattern`. -* `extensions` - An array of filename extensions that should be checked for code. The default is an array containing just `".js"`. Corresponds to `--ext`. It is only used in conjunction with directories, not with filenames, glob patterns or when using `executeOnText()`. -* `fix` - A boolean or a function (default: `false`). If a function, it will be passed each linting message and should return a boolean indicating whether the fix should be included with the output report (errors and warnings will not be listed if fixed). Files on disk are never changed regardless of the value of `fix`. To persist changes to disk, call [`outputFixes()`](#cliengineoutputfixes). -* `fixTypes` - An array of rule types for which fixes should be applied (default: `null`). This array acts like a filter, only allowing rules of the given types to apply fixes. Possible array values are `"problem"`, `"suggestion"`, and `"layout"`. -* `globals` - An array of global variables to declare (default: empty array). Corresponds to `--global`, and similarly supports passing `'name:true'` to denote a writeable global. Note: This differs from `.eslintrc.*` / `baseConfig`, where `globals` is an object. -* `ignore` - False disables use of `.eslintignore`, `ignorePath` and `ignorePattern` (default: true). Corresponds to `--no-ignore`. -* `ignorePath` - The ignore file to use instead of `.eslintignore` (default: null). Corresponds to `--ignore-path`. -* `ignorePattern` - Glob patterns for paths to ignore. String or array of strings. -* `parser` - Specify the parser to be used (default: `espree`). Corresponds to `--parser`. -* `parserOptions` - An object containing parser options (default: empty object). Corresponds to `--parser-options`. -* `plugins` - An array of plugins to load (default: empty array). Corresponds to `--plugin`. -* `reportUnusedDisableDirectives` - When set to `true`, adds reported errors for unused `eslint-disable` directives when no problems would be reported in the disabled area anyway (default: false). Corresponds to `--report-unused-disable-directives`. -* `resolvePluginsRelativeTo` - Determines the folder where plugins should be resolved from. Should be used when an integration installs plugins and uses those plugins to lint code on behalf of the end user. Corresponds to `--resolve-plugins-relative-to`. -* `rulePaths` - An array of directories to load custom rules from (default: empty array). Corresponds to `--rulesdir`. -* `rules` - An object of rules to use (default: null). Corresponds to `--rule`. -* `useEslintrc` - Set to false to disable use of `.eslintrc` files (default: true). Corresponds to `--no-eslintrc`. -* `globInputPaths` - Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file. - -To programmatically set `.eslintrc.*` options not supported above (such as `extends`, -`overrides` and `settings`), define them in a config object passed to `baseConfig` instead. - -For example: - -```js -const CLIEngine = require("eslint").CLIEngine; - -const cli = new CLIEngine({ - baseConfig: { - extends: ["eslint-config-shared"], - settings: { - sharedData: "Hello" - } - }, - envs: ["browser", "mocha"], - useEslintrc: false, - rules: { - semi: 2 - } -}); -``` - -In this example, a new `CLIEngine` instance is created that extends a configuration called -`"eslint-config-shared"`, a setting named `"sharedData"` and two environments (`"browser"` -and `"mocha"`) are defined, loading of `.eslintrc` and `package.json` files are disabled, -and the `semi` rule enabled as an error. You can then call methods on `cli` and these options -will be used to perform the correct action. - -Note: Currently `CLIEngine` does not validate options passed to it, but may start doing so in the future. - -### CLIEngine#executeOnFiles() - -If you want to lint one or more files, use the `executeOnFiles()` method. This method accepts a single argument, which is an array of files and/or directories to traverse for files. You can pass the same values as you would using the ESLint command line interface, such as `"."` to search all JavaScript files in the current directory. Here's an example: - -```js -const CLIEngine = require("eslint").CLIEngine; - -const cli = new CLIEngine({ - envs: ["browser", "mocha"], - useEslintrc: false, - rules: { - semi: 2 - } -}); - -// lint myfile.js and all files in lib/ -const report = cli.executeOnFiles(["myfile.js", "lib/"]); -``` - -The return value is an object containing the results of the linting operation. Here's an example of a report object: - -```js -{ - results: [ - { - filePath: "/Users/eslint/project/myfile.js", - messages: [{ - ruleId: "semi", - severity: 2, - message: "Missing semicolon.", - line: 1, - column: 13, - nodeType: "ExpressionStatement", - fix: { range: [12, 12], text: ";" } - }, { - ruleId: "no-useless-escape", - severity: 1, - message: "disallow unnecessary escape characters", - line: 1, - column: 10, - nodeType: "ExpressionStatement", - suggestions: [{ - desc: "Remove unnecessary escape. This maintains the current functionality.", - fix: { range: [9, 10], text: "" } - }, { - desc: "Escape backslash to include it in the RegExp.", - fix: { range: [9, 9], text: "\\" } - }] - }], - errorCount: 1, - warningCount: 1, - fixableErrorCount: 1, - fixableWarningCount: 0, - source: "\"use strict\"\n" - } - ], - errorCount: 1, - warningCount: 0, - fixableErrorCount: 1, - fixableWarningCount: 0, - usedDeprecatedRules: [] -} -``` - -You can also pass `fix: true` when instantiating the `CLIEngine` in order to have it figure out what fixes can be applied. - -```js -const CLIEngine = require("eslint").CLIEngine; - -const cli = new CLIEngine({ - envs: ["browser", "mocha"], - fix: true, // difference from last example - useEslintrc: false, - rules: { - semi: 2, - quotes: [2, "double"] - } -}); - -// lint myfile.js and all files in lib/ -const report = cli.executeOnFiles(["myfile.js", "lib/"]); -``` - -```js -{ - results: [ - { - filePath: "/Users/eslint/project/myfile.js", - messages: [ - { - ruleId: "semi", - severity: 2, - message: "Missing semicolon.", - line: 1, - column: 13, - nodeType: "ExpressionStatement", - fix: { range: [12, 12], text: ";" } - }, - { - ruleId: "func-name-matching", - severity: 2, - message: "Function name `bar` should match variable name `foo`", - line: 2, - column: 5, - nodeType: "VariableDeclarator" - } - ], - errorCount: 2, - warningCount: 0, - fixableErrorCount: 1, - fixableWarningCount: 0, - output: "\"use strict\";\nvar foo = function bar() {};\nfoo();\n" - } - ], - errorCount: 2, - warningCount: 0, - fixableErrorCount: 1, - fixableWarningCount: 0, - usedDeprecatedRules: [] -} -``` - -If the operation ends with a parsing error, you will get a single message for this file, with `fatal: true` added as an extra property. - -```js -{ - results: [ - { - filePath: "./myfile.js", - messages: [ - { - ruleId: null, - fatal: true, - severity: 2, - message: "Parsing error: Unexpected token foo", - line: 1, - column: 10 - } - ], - errorCount: 1, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - source: "function foo() {}" - } - ], - errorCount: 1, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - usedDeprecatedRules: [] -} -``` - -The top-level report object has a `results` array containing all linting results for files that had warnings or errors (any files that did not produce a warning or error are omitted). Each file result includes: - -* `filePath` - Path to the given file. -* `messages` - Array containing the result of calling `linter.verify()` on the given file. -* `errorCount` and `warningCount` - The exact number of errors and warnings respectively on the given file. -* `source` - The source code for the given file. This property is omitted if this file has no errors/warnings or if the `output` property is present. -* `output` - The source code for the given file with as many fixes applied as possible, so you can use that to rewrite the files if necessary. This property is omitted if no fix is available. - -The top-level report object also has `errorCount` and `warningCount` which give the exact number of errors and warnings respectively on all the files. Additionally, `usedDeprecatedRules` signals any deprecated rules used and their replacement (if available). Specifically, it is an array of objects with properties like so: - -* `ruleId` - The name of the rule (e.g. `indent-legacy`). -* `replacedBy` - An array of rules that replace the deprecated rule (e.g. `["indent"]`). - -Once you get a report object, it's up to you to determine how to output the results. Fixes will not be automatically applied to the files, even if you set `fix: true` when constructing the `CLIEngine` instance. To apply fixes to the files, call [`outputFixes`](#cliengineoutputfixes). - -### CLIEngine#resolveFileGlobPatterns() - -You can pass filesystem-style or glob patterns to ESLint and have it function properly. In order to achieve this, ESLint must resolve non-glob patterns into glob patterns before determining which files to execute on. The `resolveFileGlobPatterns()` methods uses the current settings from `CLIEngine` to resolve non-glob patterns into glob patterns. Pass an array of patterns that might be passed to the ESLint CLI and it will return an array of glob patterns that mean the same thing. Here's an example: - -```js -const CLIEngine = require("eslint").CLIEngine; - -const cli = new CLIEngine({ -}); - -// pass an array of patterns -const globPatterns = cli.resolveFileGlobPatterns(["."]); -console.log(globPatterns[i]); // ["**/*.js"] -``` - -### CLIEngine#getConfigForFile() - -If you want to retrieve a configuration object for a given file, use the `getConfigForFile()` method. This method accepts one argument, a file path, and returns an object represented the calculated configuration of the file. Here's an example: - -```js -const CLIEngine = require("eslint").CLIEngine; - -const cli = new CLIEngine({ - envs: ["browser", "mocha"], - useEslintrc: false, - rules: { - semi: 2 - } -}); - -const config = cli.getConfigForFile("myfile.js"); -``` - -Once you have the configuration information, you can pass it into the `linter` object: - -```js -const CLIEngine = require("eslint").CLIEngine, - Linter = require("eslint").Linter; - -const linter = new Linter(); -const cli = new CLIEngine({ - envs: ["browser", "mocha"], - useEslintrc: false, - rules: { - semi: 2 - } -}); - -const config = cli.getConfigForFile("myfile.js"); - -const messages = linter.verify('var foo;', config); -``` - -### CLIEngine#executeOnText() - -If you already have some text to lint, then you can use the `executeOnText()` method to lint that text. The linter will assume that the text is a file in the current working directory, and so will still obey any `.eslintrc` and `.eslintignore` files that may be present. Here's an example: - -```js -const CLIEngine = require("eslint").CLIEngine; - -const cli = new CLIEngine({ - envs: ["browser", "mocha"], - useEslintrc: false, - rules: { - semi: 2 - } -}); - -// Lint the supplied text and optionally set a filename that is displayed in the report -const report = cli.executeOnText("var foo = 'bar';", "foo.js"); - -// In addition to the above, warn if the resolved file name is ignored. -const reportAndWarnOnIgnoredFile = cli.executeOnText("var foo = 'bar';", "foo.js", true); -``` - -The `report` returned from `executeOnText()` is in the same format as from `executeOnFiles()`, but there is only ever one result in `report.results`. - -If a filename in the optional second parameter matches a file that is configured to be ignored, then this function returns no errors or warnings. The method includes an additional optional boolean third parameter. When `true`, a resolved file name that is ignored will return a warning. - -### CLIEngine#addPlugin() - -Loads a plugin from configuration object with specified name. Name can include plugin prefix ("eslint-plugin-") - -```js -const CLIEngine = require("eslint").CLIEngine; -const cli = new CLIEngine({ - ignore: true -}); -cli.addPlugin("eslint-plugin-processor", { - processors: { - ".txt": { - preprocess: function(text) { - return [text]; - }, - postprocess: function(messages) { - return messages[0]; - } - } - } -}); -``` - -### CLIEngine#isPathIgnored() - -Checks if a given path is ignored by ESLint. - -```js -const CLIEngine = require("eslint").CLIEngine; - -const cli = new CLIEngine({ - ignore: true, - ignorePath: ".customIgnoreFile" -}); - -const isIgnored = cli.isPathIgnored("foo/bar.js"); -``` - -### CLIEngine#getFormatter() - -Retrieves a formatter, which you can then use to format a report object. The argument is either the name of a built-in formatter: - -* "[checkstyle](../user-guide/formatters#checkstyle)" -* "[codeframe](../user-guide/formatters#codeframe)" -* "[compact](../user-guide/formatters#compact)" -* "[html](../user-guide/formatters#html)" -* "[jslint-xml](../user-guide/formatters#jslint-xml)" -* "[json](../user-guide/formatters#json)" -* "[junit](../user-guide/formatters#junit)" -* "[stylish](../user-guide/formatters#stylish)" (the default) -* "[table](../user-guide/formatters#table)" -* "[tap](../user-guide/formatters#tap)" -* "[unix](../user-guide/formatters#unix)" -* "[visualstudio](../user-guide/formatters#visualstudio)" - -or the full path to a JavaScript file containing a custom formatter. You can also omit the argument to retrieve the default formatter. - -```js -const CLIEngine = require("eslint").CLIEngine; - -const cli = new CLIEngine({ - envs: ["browser", "mocha"], - useEslintrc: false, - rules: { - semi: 2 - } -}); - -// lint myfile.js and all files in lib/ -const report = cli.executeOnFiles(["myfile.js", "lib/"]); - -// get the default formatter -const formatter = cli.getFormatter(); - -// Also could do... -// const formatter = cli.getFormatter("compact"); -// const formatter = cli.getFormatter("./my/formatter.js"); - -// output to console -console.log(formatter(report.results)); -``` - -**Note:** Also available as a static function on `CLIEngine`. - -```js -// get the default formatter by calling the static function -const formatter = CLIEngine.getFormatter(); -``` - -**Important:** You must pass in the `results` property of the report. Passing in `report` directly will result in an error. - -### CLIEngine#getErrorResults() - -This is a static function on `CLIEngine`. It can be used to filter out all the non error messages from the report object. - -```js -const CLIEngine = require("eslint").CLIEngine; - -const cli = new CLIEngine({ - envs: ["browser", "mocha"], - useEslintrc: false, - rules: { - semi: 2 - } -}); - -// lint myfile.js and all files in lib/ -const report = cli.executeOnFiles(["myfile.js", "lib/"]); - -// only get the error messages -const errorReport = CLIEngine.getErrorResults(report.results) -``` - -**Important:** You must pass in the `results` property of the report. Passing in `report` directly will result in an error. - -### CLIEngine#outputFixes() - -This is a static function on `CLIEngine` that is used to output fixes from `report` to disk. It does by looking for files that have an `output` property in their results. Here's an example: - -```js -const CLIEngine = require("eslint").CLIEngine; - -const cli = new CLIEngine({ - envs: ["browser", "mocha"], - fix: true, - useEslintrc: false, - rules: { - semi: 2 - } -}); - -// lint myfile.js and all files in lib/ -const report = cli.executeOnFiles(["myfile.js", "lib/"]); - -// output fixes to disk -CLIEngine.outputFixes(report); -``` - -### CLIEngine#getRules() - -This method returns a map of all loaded rules. Under the hood, it calls [Linter#getRules](#lintergetrules). - -```js -const CLIEngine = require("eslint").CLIEngine; -const cli = new CLIEngine(); - -cli.getRules(); - -/* -Map { - 'accessor-pairs' => { meta: { docs: [Object], schema: [Array] }, create: [Function: create] }, - 'array-bracket-newline' => { meta: { docs: [Object], schema: [Array] }, create: [Function: create] }, - ... -} -*/ -``` - - -### CLIEngine.version - -`CLIEngine` has a static `version` property containing the semantic version number of ESLint that it comes from. - -```js -require("eslint").CLIEngine.version; // '4.5.0' -``` - --- ## RuleTester @@ -1233,9 +750,11 @@ The `RuleTester#run()` method is used to run the tests. It should be passed the A test case is an object with the following properties: +* `name` (string, optional): The name to use for the test case, to make it easier to find * `code` (string, required): The source code that the rule should be run on * `options` (array, optional): The options passed to the rule. The rule severity should not be included in this list. * `filename` (string, optional): The filename for the given case (useful for rules that make assertions about filenames). +* `only` (boolean, optional): Run this case exclusively for debugging in supported test frameworks. In addition to the properties above, invalid test cases can also have the following properties: @@ -1335,10 +854,13 @@ ruleTester.run("my-rule-for-no-foo", rule, { `RuleTester` depends on two functions to run tests: `describe` and `it`. These functions can come from various places: 1. If `RuleTester.describe` and `RuleTester.it` have been set to function values, `RuleTester` will use `RuleTester.describe` and `RuleTester.it` to run tests. You can use this to customize the behavior of `RuleTester` to match a test framework that you're using. -1. Otherwise, if `describe` and `it` are present as globals, `RuleTester` will use `global.describe` and `global.it` to run tests. This allows `RuleTester` to work when using frameworks like [Mocha](https://mochajs.org/) without any additional configuration. -1. Otherwise, `RuleTester#run` will simply execute all of the tests in sequence, and will throw an error if one of them fails. This means you can simply execute a test file that calls `RuleTester.run` using `node`, without needing a testing framework. -`RuleTester#run` calls the `describe` function with two arguments: a string describing the rule, and a callback function. The callback calls the `it` function with a string describing the test case, and a test function. The test function will return successfully if the test passes, and throw an error if the test fails. (Note that this is the standard behavior for test suites when using frameworks like [Mocha](https://mochajs.org/); this information is only relevant if you plan to customize `RuleTester.it` and `RuleTester.describe`.) + If `RuleTester.itOnly` has been set to a function value, `RuleTester` will call `RuleTester.itOnly` instead of `RuleTester.it` to run cases with `only: true`. If `RuleTester.itOnly` is not set but `RuleTester.it` has an `only` function property, `RuleTester` will fall back to `RuleTester.it.only`. + +2. Otherwise, if `describe` and `it` are present as globals, `RuleTester` will use `global.describe` and `global.it` to run tests and `global.it.only` to run cases with `only: true`. This allows `RuleTester` to work when using frameworks like [Mocha](https://mochajs.org/) without any additional configuration. +3. Otherwise, `RuleTester#run` will simply execute all of the tests in sequence, and will throw an error if one of them fails. This means you can simply execute a test file that calls `RuleTester.run` using `Node.js`, without needing a testing framework. + +`RuleTester#run` calls the `describe` function with two arguments: a string describing the rule, and a callback function. The callback calls the `it` function with a string describing the test case, and a test function. The test function will return successfully if the test passes, and throw an error if the test fails. The signature for `only` is the same as `it`. `RuleTester` calls either `it` or `only` for every case even when some cases have `only: true`, and the test framework is responsible for implementing test case exclusivity. (Note that this is the standard behavior for test suites when using frameworks like [Mocha](https://mochajs.org/); this information is only relevant if you plan to customize `RuleTester.describe`, `RuleTester.it`, or `RuleTester.itOnly`.) Example of customizing `RuleTester`: @@ -1374,14 +896,6 @@ ruleTester.run("my-rule", myRule, { --- -## Deprecated APIs - -* `cli` - the `cli` object has been deprecated in favor of `CLIEngine`. As of v1.0.0, `cli` is no longer exported and should not be used by external tools. -* `linter` - the `linter` object has been deprecated in favor of `Linter` as of v4.0.0. -* `CLIEngine` - the `CLIEngine` class has been deprecated in favor of the `ESLint` class as of v7.0.0. - ---- - [configuration object]: ../user-guide/configuring [builtin-formatters]: https://eslint.org/docs/user-guide/formatters/ [thirdparty-formatters]: https://www.npmjs.com/search?q=eslintformatter @@ -1389,6 +903,7 @@ ruleTester.run("my-rule", myRule, { [eslint-constructor]: #-new-eslintoptions [eslint-lintfiles]: #-eslintlintfilespatterns [eslint-linttext]: #-eslintlinttextcode-options +[eslint-getrulesmetaforresults]: #-eslintgetrulesmetaforresultsresults [eslint-calculateconfigforfile]: #-eslintcalculateconfigforfilefilepath [eslint-ispathignored]: #-eslintispathignoredfilepath [eslint-loadformatter]: #-eslintloadformatternameorpath diff --git a/eslint/docs/developer-guide/scope-manager-interface.md b/eslint/docs/developer-guide/scope-manager-interface.md index 3ee5b23..2762f74 100644 --- a/eslint/docs/developer-guide/scope-manager-interface.md +++ b/eslint/docs/developer-guide/scope-manager-interface.md @@ -77,7 +77,7 @@ Those members are defined but not used in ESLint. #### type * **Type:** `string` -* **Description:** The type of this scope. This is one of `"block"`, `"catch"`, `"class"`, `"for"`, `"function"`, `"function-expression-name"`, `"global"`, `"module"`, `"switch"`, `"with"` +* **Description:** The type of this scope. This is one of `"block"`, `"catch"`, `"class"`, `"class-field-initializer"`, `"class-static-block"`, `"for"`, `"function"`, `"function-expression-name"`, `"global"`, `"module"`, `"switch"`, `"with"`. #### isStrict @@ -97,7 +97,9 @@ Those members are defined but not used in ESLint. #### variableScope * **Type:** `Scope` -* **Description:** The scope which hosts variables which are defined by `var` declarations. +* **Description:** The nearest ancestor whose `type` is one of `"class-field-initializer"`, `"class-static-block"`, `"function"`, `"global"`, or `"module"`. For the aforementioned scopes this is a self-reference. + +> This represents the lowest enclosing function or top-level scope. Class field initializers and class static blocks are implicit functions. Historically, this was the scope which hosts variables that are defined by `var` declarations, and thus the name `variableScope`. #### block diff --git a/eslint/docs/developer-guide/selectors.md b/eslint/docs/developer-guide/selectors.md index afe6600..78bb260 100644 --- a/eslint/docs/developer-guide/selectors.md +++ b/eslint/docs/developer-guide/selectors.md @@ -31,7 +31,7 @@ The following selectors are supported: * wildcard (matches all nodes): `*` * attribute existence: `[attr]` * attribute value: `[attr="foo"]` or `[attr=123]` -* attribute regex: `[attr=/foo.*/]` +* attribute regex: `[attr=/foo.*/]` (with some [known issues](#known-issues)) * attribute conditions: `[attr!="foo"]`, `[attr>2]`, `[attr<3]`, `[attr>=2]`, or `[attr<=3]` * nested attribute: `[attr.level2="foo"]` * field: `FunctionDeclaration > Identifier.id` @@ -131,3 +131,7 @@ Or you can enforce that calls to `setTimeout` always have two arguments: ``` Using selectors in the `no-restricted-syntax` rule can give you a lot of control over problematic patterns in your codebase, without needing to write custom rules to detect each pattern. + +### Known issues + +Due to a [bug](https://github.com/estools/esquery/issues/68) in [esquery](https://github.com/estools/esquery), regular expressions that contain a forward-slash character `/` aren't properly parsed, so `[value=/some\/path/]` will be a syntax error. As a [workaround](https://github.com/estools/esquery/issues/68), you can replace the `/` character with its unicode counterpart, like so: `[value=/some\\u002Fpath/]`. diff --git a/eslint/docs/developer-guide/unit-tests.md b/eslint/docs/developer-guide/unit-tests.md index 281c396..46d77b5 100644 --- a/eslint/docs/developer-guide/unit-tests.md +++ b/eslint/docs/developer-guide/unit-tests.md @@ -10,11 +10,29 @@ This automatically starts Mocha and runs all tests in the `tests` directory. You ## Running Individual Tests -If you want to quickly run just one test, you can do so by running Mocha directly and passing in the filename. For example: +If you want to quickly run just one test file, you can do so by running Mocha directly and passing in the filename. For example: npm run test:cli tests/lib/rules/no-wrap-func.js -Running individual tests is useful when you're working on a specific bug and iterating on the solution. You should be sure to run `npm test` before submitting a pull request. +If you want to run just one or a subset of `RuleTester` test cases, add `only: true` to each test case or wrap the test case in `RuleTester.only(...)` to add it automatically: + +```js +ruleTester.run("my-rule", myRule, { + valid: [ + RuleTester.only("const valid = 42;"), + // Other valid cases + ], + invalid: [ + { + code: "const invalid = 42;", + only: true, + }, + // Other invalid cases + ] +}) +``` + +Running individual tests is useful when you're working on a specific bug and iterating on the solution. You should be sure to run `npm test` before submitting a pull request. `npm test` uses Mocha's `--forbid-only` option to prevent `only` tests from passing full test runs. ## More Control on Unit Testing diff --git a/eslint/docs/developer-guide/working-with-plugins.md b/eslint/docs/developer-guide/working-with-plugins.md index 3809e37..bea4702 100644 --- a/eslint/docs/developer-guide/working-with-plugins.md +++ b/eslint/docs/developer-guide/working-with-plugins.md @@ -216,6 +216,14 @@ The plugin support was introduced in ESLint version `0.8.0`. Ensure the `peerDep ESLint provides the [`RuleTester`](/docs/developer-guide/nodejs-api.md#ruletester) utility to make it easy to test the rules of your plugin. +### Linting + +ESLint plugins should be linted too! It's suggested to lint your plugin with the `recommended` configurations of: + +* [eslint](https://www.npmjs.com/package/eslint) +* [eslint-plugin-eslint-plugin](https://www.npmjs.com/package/eslint-plugin-eslint-plugin) +* [eslint-plugin-node](https://www.npmjs.com/package/eslint-plugin-node) + ## Share Plugins In order to make your plugin available to the community you have to publish it on npm. diff --git a/eslint/docs/developer-guide/working-with-rules-deprecated.md b/eslint/docs/developer-guide/working-with-rules-deprecated.md index 5265cb5..63d7bb2 100644 --- a/eslint/docs/developer-guide/working-with-rules-deprecated.md +++ b/eslint/docs/developer-guide/working-with-rules-deprecated.md @@ -493,8 +493,8 @@ To keep the linting process efficient and unobtrusive, it is useful to verify th The `npm run perf` command gives a high-level overview of ESLint running time with default rules (`eslint:recommended`) enabled. ```bash -$ git checkout master -Switched to branch 'master' +$ git checkout main +Switched to branch 'main' $ npm run perf CPU Speed is 2200 with multiplier 7500000 diff --git a/eslint/docs/developer-guide/working-with-rules.md b/eslint/docs/developer-guide/working-with-rules.md index 9dadae0..ee5a560 100644 --- a/eslint/docs/developer-guide/working-with-rules.md +++ b/eslint/docs/developer-guide/working-with-rules.md @@ -24,6 +24,9 @@ Here is the basic format of the source file for a rule: // Rule Definition //------------------------------------------------------------------------------ +/** + * @type {import('eslint').Rule.RuleModule} + */ module.exports = { meta: { type: "suggestion", @@ -61,15 +64,18 @@ The source file for a rule exports an object with the following properties. * `description` (string) provides the short description of the rule in the [rules index](../rules/) * `category` (string) specifies the heading under which the rule is listed in the [rules index](../rules/) * `recommended` (boolean) is whether the `"extends": "eslint:recommended"` property in a [configuration file](../user-guide/configuring/configuration-files.md#extending-configuration-files) enables the rule - * `url` (string) specifies the URL at which the full documentation can be accessed - * `suggestion` (boolean) specifies whether rules can return suggestions (defaults to false if omitted) + * `url` (string) specifies the URL at which the full documentation can be accessed (enabling code editors to provide a helpful link on highlighted rule violations) In a custom rule or plugin, you can omit `docs` or include any properties that you need in it. -* `fixable` (string) is either `"code"` or `"whitespace"` if the `--fix` option on the [command line](../user-guide/command-line-interface.md#fix) automatically fixes problems reported by the rule +* `fixable` (string) is either `"code"` or `"whitespace"` if the `--fix` option on the [command line](../user-guide/command-line-interface.md#--fix) automatically fixes problems reported by the rule **Important:** the `fixable` property is mandatory for fixable rules. If this property isn't specified, ESLint will throw an error whenever the rule attempts to produce a fix. Omit the `fixable` property if the rule is not fixable. +* `hasSuggestions` (boolean) specifies whether rules can return suggestions (defaults to `false` if omitted) + + **Important:** the `hasSuggestions` property is mandatory for rules that provide suggestions. If this property isn't set to `true`, ESLint will throw an error whenever the rule attempts to produce a suggestion. Omit the `hasSuggestions` property if the rule does not provide suggestions. + * `schema` (array) specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../user-guide/configuring/rules.md#configuring-rules) * `deprecated` (boolean) indicates whether the rule has been deprecated. You may omit the `deprecated` property if the rule has not been deprecated. @@ -127,7 +133,7 @@ The `context` object contains additional functionality that is helpful for rules Additionally, the `context` object has the following methods: * `getAncestors()` - returns an array of the ancestors of the currently-traversed node, starting at the root of the AST and continuing through the direct parent of the current node. This array does not include the currently-traversed node itself. -* `getCwd()` - returns the `cwd` passed to [Linter](./nodejs-api.md#Linter). It is a path to a directory that should be considered as the current working directory. +* `getCwd()` - returns the `cwd` passed to [Linter](./nodejs-api.md#linter). It is a path to a directory that should be considered as the current working directory. * `getDeclaredVariables(node)` - returns a list of [variables](./scope-manager-interface.md#variable-interface) declared by the given node. This information can be used to track references to variables. * If the node is a `VariableDeclaration`, all variables declared in the declaration are returned. * If the node is a `VariableDeclarator`, all variables declared in the declarator are returned. @@ -315,6 +321,8 @@ The `fixer` object has the following methods: * `replaceText(nodeOrToken, text)` - replaces the text in the given node or token * `replaceTextRange(range, text)` - replaces the text in the given range +A range is a two-item array containing character indices inside of the source code. The first item is the start of the range (inclusive) and the second item is the end of the range (exclusive). Every node and token has a `range` property to identify the source code range they represent. + The above methods return a `fixing` object. The `fix()` function can return the following values: @@ -346,6 +354,24 @@ Best practices for fixes: * This fixer can just select a quote type arbitrarily. If it guesses wrong, the resulting code will be automatically reported and fixed by the [`quotes`](/docs/rules/quotes.md) rule. +Note: Making fixes as small as possible is a best practice, but in some cases it may be correct to extend the range of the fix in order to intentionally prevent other rules from making fixes in a surrounding range in the same pass. For instance, if replacement text declares a new variable, it can be useful to prevent other changes in the scope of the variable as they might cause name collisions. + +The following example replaces `node` and also ensures that no other fixes will be applied in the range of `node.parent` in the same pass: + +```js +context.report({ + node, + message, + *fix(fixer) { + yield fixer.replaceText(node, replacementText); + + // extend range of the fix to the range of `node.parent` + yield fixer.insertTextBefore(node.parent, ""); + yield fixer.insertTextAfter(node.parent, ""); + } +}); +``` + ### Providing Suggestions In some cases fixes aren't appropriate to be automatically applied, for example, if a fix potentially changes functionality or if there are multiple valid ways to fix a rule depending on the implementation intent (see the best practices for [applying fixes](#applying-fixes) listed above). In these cases, there is an alternative `suggest` option on `context.report()` that allows other tools, such as editors, to expose helpers for users to manually apply a suggestion. @@ -376,6 +402,8 @@ context.report({ {% endraw %} ``` +**Important:** The `meta.hasSuggestions` property is mandatory for rules that provide suggestions. ESLint will throw an error if a rule attempts to produce a suggestion but does not [export](#rule-basics) this property. + Note: Suggestions will be applied as a stand-alone change, without triggering multipass fixes. Each suggestion should focus on a singular change in the code and should not try to conform to user defined styles. For example, if a suggestion is adding a new statement into the codebase, it should not try to match correct indentation, or conform to user preferences on presence/absence of semicolons. All of those things can be corrected by multipass autofix when the user triggers it. Best practices for suggestions: @@ -387,7 +415,7 @@ Suggestions are intended to provide fixes. ESLint will automatically remove the #### Suggestion `messageId`s -Instead of using a `desc` key for suggestions a `messageId` can be used instead. This works the same way as `messageId`s for the overall error (see [messageIds](#messageIds)). Here is an example of how to use it in a rule: +Instead of using a `desc` key for suggestions a `messageId` can be used instead. This works the same way as `messageId`s for the overall error (see [messageIds](#messageids)). Here is an example of how to use it in a rule: ```js {% raw %} @@ -397,7 +425,8 @@ module.exports = { unnecessaryEscape: "Unnecessary escape character: \\{{character}}.", removeEscape: "Remove the `\\`. This maintains the current functionality.", escapeBackslash: "Replace the `\\` with `\\\\` to include the actual backslash character." - } + }, + hasSuggestions: true }, create: function(context) { // ... @@ -438,7 +467,8 @@ module.exports = { messages: { unnecessaryEscape: "Unnecessary escape character: \\{{character}}.", removeEscape: "Remove `\\` before {{character}}.", - } + }, + hasSuggestions: true }, create: function(context) { // ... @@ -501,7 +531,7 @@ module.exports = { }; ``` -Once you have an instance of `SourceCode`, you can use the methods on it to work with the code: +Once you have an instance of `SourceCode`, you can use the following methods on it to work with the code: * `getText(node)` - returns the source code for the given node. Omit `node` to get the whole source. * `getAllComments()` - returns an array of all comments in the source. @@ -667,8 +697,8 @@ To keep the linting process efficient and unobtrusive, it is useful to verify th When developing in the ESLint core repository, the `npm run perf` command gives a high-level overview of ESLint running time with all core rules enabled. ```bash -$ git checkout master -Switched to branch 'master' +$ git checkout main +Switched to branch 'main' $ npm run perf CPU Speed is 2200 with multiplier 7500000 diff --git a/eslint/docs/rules/block-scoped-var.md b/eslint/docs/rules/block-scoped-var.md index 75c7db0..9c308fe 100644 --- a/eslint/docs/rules/block-scoped-var.md +++ b/eslint/docs/rules/block-scoped-var.md @@ -41,6 +41,15 @@ function doFor() { } console.log(y); } + +class C { + static { + if (something) { + var build = true; + } + build = false; + } +} ``` Examples of **correct** code for this rule: @@ -85,6 +94,15 @@ function doFor() { console.log(y); } } + +class C { + static { + var build = false; + if (something) { + build = true; + } + } +} ``` ## Further Reading diff --git a/eslint/docs/rules/block-spacing.md b/eslint/docs/rules/block-spacing.md index aef7c33..04c7aeb 100644 --- a/eslint/docs/rules/block-spacing.md +++ b/eslint/docs/rules/block-spacing.md @@ -23,6 +23,10 @@ if (foo) { bar = 0;} function baz() {let i = 0; return i; } + +class C { + static {this.bar = 0;} +} ``` Examples of **correct** code for this rule with the default `"always"` option: @@ -32,6 +36,10 @@ Examples of **correct** code for this rule with the default `"always"` option: function foo() { return true; } if (foo) { bar = 0; } + +class C { + static { this.bar = 0; } +} ``` ### never @@ -43,6 +51,10 @@ Examples of **incorrect** code for this rule with the `"never"` option: function foo() { return true; } if (foo) { bar = 0;} + +class C { + static { this.bar = 0; } +} ``` Examples of **correct** code for this rule with the `"never"` option: @@ -52,6 +64,10 @@ Examples of **correct** code for this rule with the `"never"` option: function foo() {return true;} if (foo) {bar = 0;} + +class C { + static {this.bar = 0;} +} ``` ## When Not To Use It diff --git a/eslint/docs/rules/brace-style.md b/eslint/docs/rules/brace-style.md index 1a5bf00..c22c790 100644 --- a/eslint/docs/rules/brace-style.md +++ b/eslint/docs/rules/brace-style.md @@ -85,6 +85,14 @@ if (foo) { else { baz(); } + +class C +{ + static + { + foo(); + } +} ``` Examples of **correct** code for this rule with the default `"1tbs"` option: @@ -112,6 +120,12 @@ try { handleError(); } +class C { + static { + foo(); + } +} + // when there are no braces, there are no problems if (foo) bar(); else if (baz) boom(); @@ -150,6 +164,12 @@ if (foo) { baz(); } else if (bar) { try { somethingRisky(); } catch(e) { handleError(); } + +class C { + static { foo(); } +} + +class D { static { foo(); } } ``` ### stroustrup @@ -177,6 +197,14 @@ try handleError(); } +class C +{ + static + { + foo(); + } +} + if (foo) { bar(); } else { @@ -211,6 +239,12 @@ catch(e) { handleError(); } +class C { + static { + foo(); + } +} + // when there are no braces, there are no problems if (foo) bar(); else if (baz) boom(); @@ -230,6 +264,12 @@ else { baz(); } try { somethingRisky(); } catch(e) { handleError(); } + +class C { + static { foo(); } +} + +class D { static { foo(); } } ``` ### allman @@ -255,6 +295,12 @@ try handleError(); } +class C { + static { + foo(); + } +} + if (foo) { bar(); } else { @@ -295,6 +341,14 @@ catch(e) handleError(); } +class C +{ + static + { + foo(); + } +} + // when there are no braces, there are no problems if (foo) bar(); else if (baz) boom(); @@ -314,6 +368,16 @@ else { baz(); } try { somethingRisky(); } catch(e) { handleError(); } + +class C +{ + static { foo(); } + + static + { foo(); } +} + +class D { static { foo(); } } ``` ## When Not To Use It diff --git a/eslint/docs/rules/class-methods-use-this.md b/eslint/docs/rules/class-methods-use-this.md index 89f6762..73efc9d 100644 --- a/eslint/docs/rules/class-methods-use-this.md +++ b/eslint/docs/rules/class-methods-use-this.md @@ -83,12 +83,21 @@ class A { static foo() { // OK. static methods aren't expected to use this. } + + static { + // OK. static blocks are exempt. + } } ``` ## Options -### Exceptions +This rule has two options: + +* `"exceptMethods"` allows specified method names to be ignored with this rule. +* `"enforceForClassFields"` enforces that functions used as instance field initializers utilize `this`. (default: `true`) + +### exceptMethods ``` "class-methods-use-this": [, { "exceptMethods": [<...exceptions>] }] @@ -110,11 +119,51 @@ class A { Examples of **correct** code for this rule when used with exceptMethods: ```js -/*eslint class-methods-use-this: ["error", { "exceptMethods": ["foo"] }] */ +/*eslint class-methods-use-this: ["error", { "exceptMethods": ["foo", "#bar"] }] */ class A { foo() { } + #bar() { + } +} +``` + +## enforceForClassFields + +``` +"class-methods-use-this": [, { "enforceForClassFields": true | false }] +``` + +The `enforceForClassFields` option enforces that arrow functions and function expressions used as instance field initializers utilize `this`. (default: `true`) + +Examples of **incorrect** code for this rule with the `{ "enforceForClassFields": true }` option (default): + +```js +/*eslint class-methods-use-this: ["error", { "enforceForClassFields": true }] */ + +class A { + foo = () => {} +} +``` + +Examples of **correct** code for this rule with the `{ "enforceForClassFields": true }` option (default): + +```js +/*eslint class-methods-use-this: ["error", { "enforceForClassFields": true }] */ + +class A { + foo = () => {this;} +} +``` + +Examples of **correct** code for this rule with the `{ "enforceForClassFields": false }` option: + +```js +/*eslint class-methods-use-this: ["error", { "enforceForClassFields": false }] */ + +class A { + foo = () => {} } ``` diff --git a/eslint/docs/rules/complexity.md b/eslint/docs/rules/complexity.md index a57b436..7955d1a 100644 --- a/eslint/docs/rules/complexity.md +++ b/eslint/docs/rules/complexity.md @@ -57,6 +57,53 @@ function b() { } ``` +Class field initializers and class static blocks are implicit functions. Therefore, their complexity is calculated separately for each initializer and each static block, and it doesn't contribute to the complexity of the enclosing code. + +Examples of additional **incorrect** code for a maximum of 2: + +```js +/*eslint complexity: ["error", 2]*/ + +class C { + x = a || b || c; // this initializer has complexity = 3 +} + +class D { // this static block has complexity = 3 + static { + if (foo) { + bar = baz || qux; + } + } +} +``` + +Examples of additional **correct** code for a maximum of 2: + +```js +/*eslint complexity: ["error", 2]*/ + +function foo() { // this function has complexity = 1 + class C { + x = a + b; // this initializer has complexity = 1 + y = c || d; // this initializer has complexity = 2 + z = e && f; // this initializer has complexity = 2 + + static p = g || h; // this initializer has complexity = 2 + static q = i ? j : k; // this initializer has complexity = 2 + + static { // this static block has complexity = 2 + if (foo) { + baz = bar; + } + } + + static { // this static block has complexity = 2 + qux = baz || quux; + } + } +} +``` + ## Options Optionally, you may specify a `max` object property: diff --git a/eslint/docs/rules/dot-notation.md b/eslint/docs/rules/dot-notation.md index a13049a..26c7775 100644 --- a/eslint/docs/rules/dot-notation.md +++ b/eslint/docs/rules/dot-notation.md @@ -46,6 +46,19 @@ var foo = { "class": "CS 101" } var x = foo["class"]; // Property name is a reserved word, square-bracket notation required ``` +Examples of additional **correct** code for the `{ "allowKeywords": false }` option: + +```js +/*eslint dot-notation: ["error", { "allowKeywords": false }]*/ + +class C { + #in; + foo() { + this.#in; // Dot notation is required for private identifiers + } +} +``` + ### allowPattern For example, when preparing data to be sent to an external API, it is often required to use property names that include underscores. If the `camelcase` rule is in effect, these [snake case](https://en.wikipedia.org/wiki/Snake_case) properties would not be allowed. By providing an `allowPattern` to the `dot-notation` rule, these snake case properties can be accessed with bracket notation. diff --git a/eslint/docs/rules/func-name-matching.md b/eslint/docs/rules/func-name-matching.md index 53be2a0..39ee411 100644 --- a/eslint/docs/rules/func-name-matching.md +++ b/eslint/docs/rules/func-name-matching.md @@ -15,6 +15,10 @@ obj.foo = function bar() {}; obj['foo'] = function bar() {}; var obj = {foo: function bar() {}}; ({['foo']: function bar() {}}); + +class C { + foo = function bar() {}; +} ``` ```js @@ -26,6 +30,10 @@ obj.foo = function foo() {}; obj['foo'] = function foo() {}; var obj = {foo: function foo() {}}; ({['foo']: function foo() {}}); + +class C { + foo = function foo() {}; +} ``` Examples of **correct** code for this rule: @@ -54,6 +62,21 @@ obj['x' + 2] = function bar(){}; var [ bar ] = [ function bar(){} ]; ({[foo]: function bar() {}}) +class C { + foo = function foo() {}; + baz = function() {}; +} + +// private names are ignored +class D { + #foo = function foo() {}; + #bar = function foo() {}; + baz() { + this.#foo = function foo() {}; + this.#foo = function bar() {}; + } +} + module.exports = function foo(name) {}; module['exports'] = function foo(name) {}; ``` @@ -81,6 +104,21 @@ obj['x' + 2] = function bar(){}; var [ bar ] = [ function bar(){} ]; ({[foo]: function bar() {}}) +class C { + foo = function bar() {}; + baz = function() {}; +} + +// private names are ignored +class D { + #foo = function foo() {}; + #bar = function foo() {}; + baz() { + this.#foo = function foo() {}; + this.#foo = function bar() {}; + } +} + module.exports = function foo(name) {}; module['exports'] = function foo(name) {}; ``` diff --git a/eslint/docs/rules/func-names.md b/eslint/docs/rules/func-names.md index 27b9cc2..ea1ba61 100644 --- a/eslint/docs/rules/func-names.md +++ b/eslint/docs/rules/func-names.md @@ -17,14 +17,14 @@ This rule can enforce or disallow the use of named function expressions. This rule has a string option: * `"always"` (default) requires function expressions to have a name -* `"as-needed"` requires function expressions to have a name, if the name cannot be assigned automatically in an ES6 environment +* `"as-needed"` requires function expressions to have a name, if the name isn't assigned automatically per the ECMAScript specification. * `"never"` disallows named function expressions, except in recursive functions, where a name is needed This rule has an object option: * `"generators": "always" | "as-needed" | "never"` * `"always"` require named generators - * `"as-needed"` require named generators if the name cannot be assigned automatically in an ES6 environment. + * `"as-needed"` require named generators if the name isn't assigned automatically per the ECMAScript specification. * `"never"` disallow named generators where possible. When a value for `generators` is not provided the behavior for generator functions falls back to the base option. @@ -98,6 +98,13 @@ const cat = { meow: function() {} } +class C { + #bar = function() {}; + baz = function() {}; +} + +quux ??= function() {}; + (function bar() { // ... }()) diff --git a/eslint/docs/rules/id-denylist.md b/eslint/docs/rules/id-denylist.md index 040f26e..071a34f 100644 --- a/eslint/docs/rules/id-denylist.md +++ b/eslint/docs/rules/id-denylist.md @@ -13,6 +13,8 @@ This rule will catch disallowed identifiers that are: - variable declarations - function declarations - object properties assigned to during object creation +- class fields +- class methods It will not catch disallowed identifiers that are: @@ -49,6 +51,22 @@ element.callback = function() { var itemSet = { data: [...] }; + +class Foo { + data = []; +} + +class Foo { + #data = []; +} + +class Foo { + callback( {); +} + +class Foo { + #callback( {); +} ``` Examples of **correct** code for this rule with sample `"data", "callback"` restricted identifiers: @@ -75,6 +93,22 @@ callback(); // all function calls are ignored foo.callback(); // all function calls are ignored foo.data; // all property names that are not assignments are ignored + +class Foo { + items = []; +} + +class Foo { + #items = []; +} + +class Foo { + method( {); +} + +class Foo { + #method( {); +} ``` ## When Not To Use It diff --git a/eslint/docs/rules/id-length.md b/eslint/docs/rules/id-length.md index e9f8d9e..d8b1b45 100644 --- a/eslint/docs/rules/id-length.md +++ b/eslint/docs/rules/id-length.md @@ -30,6 +30,9 @@ var myObj = { a: 1 }; (a) => { a * a }; class x { } class Foo { x() {} } +class Foo { #x() {} } +class Foo { x = 1 } +class Foo { #x = 1 } function foo(...x) { } function foo([x]) { } var [x] = arr; @@ -61,6 +64,9 @@ var myObj = { apple: 1 }; function foo(num = 0) { } class MyClass { } class Foo { method() {} } +class Foo { #method() {} } +class Foo { field = 1 } +class Foo { #field = 1 } function foo(...args) { } function foo([longName]) { } var { prop } = {}; diff --git a/eslint/docs/rules/id-match.md b/eslint/docs/rules/id-match.md index 0d9deeb..5bb2a1d 100644 --- a/eslint/docs/rules/id-match.md +++ b/eslint/docs/rules/id-match.md @@ -35,9 +35,20 @@ var MY_FAVORITE_COLOR = "#112C85"; function do_something() { // ... } + obj.do_something = function() { // ... }; + +class My_Class {} + +class myClass { + do_something() {} +} + +class myClass { + #do_something() {} +} ``` Examples of **correct** code for this rule with the `"^[a-z]+([A-Z][a-z]+)*$"` option: @@ -52,13 +63,26 @@ do_something(); var obj = { my_pref: 1 }; + +class myClass {} + +class myClass { + doSomething() {} +} + +class myClass { + #doSomething() {} +} ``` This rule has an object option: -* `"properties": true` requires object properties to match the specified regular expression +* `"properties": false` (default) does not check object properties +* `"properties": true` requires object literal properties and member expression assignment properties to match the specified regular expression +* `"classFields": false` (default) does not class field names +* `"classFields": true` requires class field names to match the specified regular expression +* `"onlyDeclarations": false` (default) requires all variable names to match the specified regular expression * `"onlyDeclarations": true` requires only `var`, `function`, and `class` declarations to match the specified regular expression -* `"onlyDeclarations": false` requires all variable names to match the specified regular expression * `"ignoreDestructuring": false` (default) enforces `id-match` for destructured identifiers * `"ignoreDestructuring": true` does not check destructured identifiers @@ -74,6 +98,22 @@ var obj = { }; ``` +### classFields + +Examples of **incorrect** code for this rule with the `"^[a-z]+([A-Z][a-z]+)*$", { "classFields": true }` options: + +```js +/*eslint id-match: ["error", "^[a-z]+([A-Z][a-z]+)*$", { "properties": true }]*/ + +class myClass { + my_pref = 1; +} + +class myClass { + #my_pref = 1; +} +``` + ### onlyDeclarations Examples of **correct** code for this rule with the `"^[a-z]+([A-Z][a-z]+)*$", { "onlyDeclarations": true }` options: diff --git a/eslint/docs/rules/indent-legacy.md b/eslint/docs/rules/indent-legacy.md index 45ef1a6..2711ccc 100644 --- a/eslint/docs/rules/indent-legacy.md +++ b/eslint/docs/rules/indent-legacy.md @@ -1,5 +1,7 @@ # enforce consistent indentation (indent-legacy) +This rule was **deprecated** in ESLint v4.0.0. + ESLint 4.0.0 introduced a rewrite of the [`indent`](/docs/rules/indent) rule, which now reports more errors than it did in previous versions. To ease the process of migrating to 4.0.0, the `indent-legacy` rule was introduced as a snapshot of the `indent` rule from ESLint 3.x. If your build is failing after the upgrade to 4.0.0, you can disable `indent` and enable `indent-legacy` as a quick fix. Eventually, you should switch back to the `indent` rule to get bugfixes and improvements in future versions. --- diff --git a/eslint/docs/rules/indent.md b/eslint/docs/rules/indent.md index 2a62660..74fb0e7 100644 --- a/eslint/docs/rules/indent.md +++ b/eslint/docs/rules/indent.md @@ -79,6 +79,8 @@ This rule has an object option: * `"FunctionExpression"` takes an object to define rules for function expressions. * `parameters` (default: 1) enforces indentation level for parameters in a function expression. This can either be a number indicating indentation level, or the string `"first"` indicating that all parameters of the expression must be aligned with the first parameter. This can also be set to `"off"` to disable checking for FunctionExpression parameters. * `body` (default: 1) enforces indentation level for the body of a function expression. +* `"StaticBlock"` takes an object to define rules for class static blocks. + * `body` (default: 1) enforces indentation level for the body of a class static block. * `"CallExpression"` takes an object to define rules for function call expressions. * `arguments` (default: 1) enforces indentation level for arguments in a call expression. This can either be a number indicating indentation level, or the string `"first"` indicating that all arguments of the expression must be aligned with the first argument. This can also be set to `"off"` to disable checking for CallExpression arguments. * `"ArrayExpression"` (default: 1) enforces indentation level for elements in arrays. It can also be set to the string `"first"`, indicating that all the elements in the array should be aligned with the first element. This can also be set to `"off"` to disable checking for array elements. @@ -484,6 +486,56 @@ var foo = function(bar, baz, } ``` +### StaticBlock + +Examples of **incorrect** code for this rule with the `2, { "StaticBlock": {"body": 1} }` option: + +```js +/*eslint indent: ["error", 2, { "StaticBlock": {"body": 1} }]*/ + +class C { + static { + foo(); + } +} +``` + +Examples of **correct** code for this rule with the `2, { "StaticBlock": {"body": 1} }` option: + +```js +/*eslint indent: ["error", 2, { "StaticBlock": {"body": 1} }]*/ + +class C { + static { + foo(); + } +} +``` + +Examples of **incorrect** code for this rule with the `2, { "StaticBlock": {"body": 2} }` option: + +```js +/*eslint indent: ["error", 2, { "StaticBlock": {"body": 2} }]*/ + +class C { + static { + foo(); + } +} +``` + +Examples of **correct** code for this rule with the `2, { "StaticBlock": {"body": 2} }` option: + +```js +/*eslint indent: ["error", 2, { "StaticBlock": {"body": 2} }]*/ + +class C { + static { + foo(); + } +} +``` + ### CallExpression Examples of **incorrect** code for this rule with the `2, { "CallExpression": {"arguments": 1} }` option: diff --git a/eslint/docs/rules/keyword-spacing.md b/eslint/docs/rules/keyword-spacing.md index ecc6bb0..8ca6739 100644 --- a/eslint/docs/rules/keyword-spacing.md +++ b/eslint/docs/rules/keyword-spacing.md @@ -19,7 +19,7 @@ However, if you want to enforce the style of spacing between the `function` keyw ## Rule Details -This rule enforces consistent spacing around keywords and keyword-like tokens: `as` (in module declarations), `async` (of async functions), `await` (of await expressions), `break`, `case`, `catch`, `class`, `const`, `continue`, `debugger`, `default`, `delete`, `do`, `else`, `export`, `extends`, `finally`, `for`, `from` (in module declarations), `function`, `get` (of getters), `if`, `import`, `in`, `instanceof`, `let`, `new`, `of` (in for-of statements), `return`, `set` (of setters), `static`, `super`, `switch`, `this`, `throw`, `try`, `typeof`, `var`, `void`, `while`, `with`, and `yield`. This rule is designed carefully not to conflict with other spacing rules: it does not apply to spacing where other rules report problems. +This rule enforces consistent spacing around keywords and keyword-like tokens: `as` (in module declarations), `async` (of async functions), `await` (of await expressions), `break`, `case`, `catch`, `class`, `const`, `continue`, `debugger`, `default`, `delete`, `do`, `else`, `export`, `extends`, `finally`, `for`, `from` (in module declarations), `function`, `get` (of getters), `if`, `import`, `in` (in for-in statements), `let`, `new`, `of` (in for-of statements), `return`, `set` (of setters), `static`, `super`, `switch`, `this`, `throw`, `try`, `typeof`, `var`, `void`, `while`, `with`, and `yield`. This rule is designed carefully not to conflict with other spacing rules: it does not apply to spacing where other rules report problems. ## Options @@ -243,13 +243,14 @@ if(foo) { ### overrides -Examples of **correct** code for this rule with the `{ "overrides": { "if": { "after": false }, "for": { "after": false }, "while": { "after": false } } }` option: +Examples of **correct** code for this rule with the `{ "overrides": { "if": { "after": false }, "for": { "after": false }, "while": { "after": false }, "static": { "after": false } } }` option: ```js /*eslint keyword-spacing: ["error", { "overrides": { "if": { "after": false }, "for": { "after": false }, - "while": { "after": false } + "while": { "after": false }, + "static": { "after": false } } }]*/ if(foo) { @@ -263,7 +264,13 @@ if(foo) { for(;;); while(true) { - //... + //... +} + +class C { + static{ + //... + } } ``` diff --git a/eslint/docs/rules/lines-around-comment.md b/eslint/docs/rules/lines-around-comment.md index ae543bd..fb01611 100644 --- a/eslint/docs/rules/lines-around-comment.md +++ b/eslint/docs/rules/lines-around-comment.md @@ -15,8 +15,8 @@ This rule has an object option: * `"afterBlockComment": true` requires an empty line after block comments * `"beforeLineComment": true` requires an empty line before line comments * `"afterLineComment": true` requires an empty line after line comments -* `"allowBlockStart": true` allows comments to appear at the start of block statements -* `"allowBlockEnd": true` allows comments to appear at the end of block statements +* `"allowBlockStart": true` allows comments to appear at the start of block statements, function bodies, classes, and class static blocks +* `"allowBlockEnd": true` allows comments to appear at the end of block statements, function bodies, classes, and class static blocks * `"allowObjectStart": true` allows comments to appear at the start of object literals * `"allowObjectEnd": true` allows comments to appear at the end of object literals * `"allowArrayStart": true` allows comments to appear at the start of array literals @@ -133,6 +133,25 @@ function foo(){ var day = "great" return day; } + +if (bar) { + // what a great and wonderful day + foo(); +} + +class C { + // what a great and wonderful day + + method() { + // what a great and wonderful day + foo(); + } + + static { + // what a great and wonderful day + foo(); + } +} ``` Examples of **correct** code for this rule with the `{ "beforeBlockComment": true, "allowBlockStart": true }` options: @@ -145,6 +164,25 @@ function foo(){ var day = "great" return day; } + +if (bar) { + /* what a great and wonderful day */ + foo(); +} + +class C { + /* what a great and wonderful day */ + + method() { + /* what a great and wonderful day */ + foo(); + } + + static { + /* what a great and wonderful day */ + foo(); + } +} ``` ### allowBlockEnd @@ -159,6 +197,26 @@ function foo(){ return day; // what a great and wonderful day } + +if (bar) { + foo(); + // what a great and wonderful day +} + +class C { + + method() { + foo(); + // what a great and wonderful day + } + + static { + foo(); + // what a great and wonderful day + } + + // what a great and wonderful day +} ``` Examples of **correct** code for this rule with the `{ "afterBlockComment": true, "allowBlockEnd": true }` option: @@ -172,6 +230,29 @@ function foo(){ /* what a great and wonderful day */ } + +if (bar) { + foo(); + + /* what a great and wonderful day */ +} + +class C { + + method() { + foo(); + + /* what a great and wonderful day */ + } + + static { + foo(); + + /* what a great and wonderful day */ + } + + /* what a great and wonderful day */ +} ``` ### allowClassStart diff --git a/eslint/docs/rules/lines-between-class-members.md b/eslint/docs/rules/lines-between-class-members.md index f950a6b..27084c2 100644 --- a/eslint/docs/rules/lines-between-class-members.md +++ b/eslint/docs/rules/lines-between-class-members.md @@ -9,6 +9,7 @@ Examples of **incorrect** code for this rule: ```js /* eslint lines-between-class-members: ["error", "always"]*/ class MyClass { + x; foo() { //... } @@ -23,6 +24,8 @@ Examples of **correct** code for this rule: ```js /* eslint lines-between-class-members: ["error", "always"]*/ class MyClass { + x; + foo() { //... } @@ -33,6 +36,17 @@ class MyClass { } ``` +Examples of additional **correct** code for this rule: + +```js +/* eslint lines-between-class-members: ["error", "always"]*/ +class MyClass { + x = 1 + + ;in = 2 +} +``` + ### Options This rule has a string option and an object option. @@ -52,12 +66,15 @@ Examples of **incorrect** code for this rule with the string option: ```js /* eslint lines-between-class-members: ["error", "always"]*/ class Foo{ + x; bar(){} baz(){} } /* eslint lines-between-class-members: ["error", "never"]*/ class Foo{ + x; + bar(){} baz(){} @@ -69,6 +86,8 @@ Examples of **correct** code for this rule with the string option: ```js /* eslint lines-between-class-members: ["error", "always"]*/ class Foo{ + x; + bar(){} baz(){} @@ -76,6 +95,7 @@ class Foo{ /* eslint lines-between-class-members: ["error", "never"]*/ class Foo{ + x; bar(){} baz(){} } @@ -86,6 +106,7 @@ Examples of **correct** code for this rule with the object option: ```js /* eslint lines-between-class-members: ["error", "always", { "exceptAfterSingleLine": true }]*/ class Foo{ + x; // single line class member bar(){} // single line class member baz(){ // multi line class member diff --git a/eslint/docs/rules/max-classes-per-file.md b/eslint/docs/rules/max-classes-per-file.md index 98ca1d1..70f3348 100644 --- a/eslint/docs/rules/max-classes-per-file.md +++ b/eslint/docs/rules/max-classes-per-file.md @@ -28,8 +28,12 @@ class Foo {} ## Options -This rule has a numeric option (defaulted to 1) to specify the -maximum number of classes. +This rule may be configured with either an object or a number. + +If the option is an object, it may contain one or both of: + +- `ignoreExpressions`: a boolean option (defaulted to `false`) to ignore class expressions. +- `max`: a numeric option (defaulted to 1) to specify the maximum number of classes. For example: @@ -39,7 +43,16 @@ For example: } ``` -Examples of **correct** code for this rule with the numeric option set to `2`: +```json +{ + "max-classes-per-file": [ + "error", + { "ignoreExpressions": true, "max": 2 } + ] +} +``` + +Examples of **correct** code for this rule with the `max` option set to `2`: ```js /* eslint max-classes-per-file: ["error", 2] */ @@ -47,3 +60,19 @@ Examples of **correct** code for this rule with the numeric option set to `2`: class Foo {} class Bar {} ``` + +Examples of **correct** code for this rule with the `ignoreExpressions` option set to `true`: + +```js +/* eslint max-classes-per-file: ["error", { ignoreExpressions: true }] */ + +class VisitorFactory { + forDescriptor(descriptor) { + return class { + visit(node) { + return `Visiting ${descriptor}.`; + } + }; + } +} +``` diff --git a/eslint/docs/rules/max-depth.md b/eslint/docs/rules/max-depth.md index b853d02..ae8121e 100644 --- a/eslint/docs/rules/max-depth.md +++ b/eslint/docs/rules/max-depth.md @@ -20,7 +20,6 @@ Examples of **incorrect** code for this rule with the default `{ "max": 4 }` opt ```js /*eslint max-depth: ["error", 4]*/ -/*eslint-env es6*/ function foo() { for (;;) { // Nested 1 deep @@ -40,7 +39,6 @@ Examples of **correct** code for this rule with the default `{ "max": 4 }` optio ```js /*eslint max-depth: ["error", 4]*/ -/*eslint-env es6*/ function foo() { for (;;) { // Nested 1 deep @@ -54,6 +52,48 @@ function foo() { } ``` +Note that class static blocks do not count as nested blocks, and that the depth in them is calculated separately from the enclosing context. + +Examples of **incorrect** code for this rule with `{ "max": 2 }` option: + +```js +/*eslint max-depth: ["error", 2]*/ + +function foo() { + if (true) { // Nested 1 deep + class C { + static { + if (true) { // Nested 1 deep + if (true) { // Nested 2 deep + if (true) { // Nested 3 deep + } + } + } + } + } + } +} +``` + +Examples of **correct** code for this rule with `{ "max": 2 }` option: + +```js +/*eslint max-depth: ["error", 2]*/ + +function foo() { + if (true) { // Nested 1 deep + class C { + static { + if (true) { // Nested 1 deep + if (true) { // Nested 2 deep + } + } + } + } + } +} +``` + ## Related Rules * [complexity](complexity.md) diff --git a/eslint/docs/rules/max-statements.md b/eslint/docs/rules/max-statements.md index 6ef87c2..c2a6733 100644 --- a/eslint/docs/rules/max-statements.md +++ b/eslint/docs/rules/max-statements.md @@ -112,6 +112,30 @@ let foo = () => { } ``` +Note that this rule does not apply to class static blocks, and that statements in class static blocks do not count as statements in the enclosing function. + +Examples of **correct** code for this rule with `{ "max": 2 }` option: + +```js +/*eslint max-statements: ["error", 2]*/ + +function foo() { + let one; + let two = class { + static { + let three; + let four; + let five; + if (six) { + let seven; + let eight; + let nine; + } + } + }; +} +``` + ### ignoreTopLevelFunctions Examples of additional **correct** code for this rule with the `{ "max": 10 }, { "ignoreTopLevelFunctions": true }` options: diff --git a/eslint/docs/rules/new-cap.md b/eslint/docs/rules/new-cap.md index 22ac577..ef8c615 100644 --- a/eslint/docs/rules/new-cap.md +++ b/eslint/docs/rules/new-cap.md @@ -112,15 +112,24 @@ var emitter = new events(); ### newIsCapExceptionPattern -Examples of additional **correct** code for this rule with the `{ "newIsCapExceptionPattern": "^person\.." }` option: +Examples of additional **correct** code for this rule with the `{ "newIsCapExceptionPattern": "^person\\.." }` option: ```js -/*eslint new-cap: ["error", { "newIsCapExceptionPattern": "^person\.." }]*/ +/*eslint new-cap: ["error", { "newIsCapExceptionPattern": "^person\\.." }]*/ var friend = new person.acquaintance(); + var bestFriend = new person.friend(); ``` +Examples of additional **correct** code for this rule with the `{ "newIsCapExceptionPattern": "\\.bar$" }` option: + +```js +/*eslint new-cap: ["error", { "newIsCapExceptionPattern": "\\.bar$" }]*/ + +var friend = new person.bar(); +``` + ### capIsNewExceptions Examples of additional **correct** code for this rule with the `{ "capIsNewExceptions": ["Person"] }` option: @@ -135,15 +144,35 @@ function foo(arg) { ### capIsNewExceptionPattern -Examples of additional **correct** code for this rule with the `{ "capIsNewExceptionPattern": "^Person\.." }` option: +Examples of additional **correct** code for this rule with the `{ "capIsNewExceptionPattern": "^person\\.." }` option: ```js -/*eslint new-cap: ["error", { "capIsNewExceptionPattern": "^Person\.." }]*/ +/*eslint new-cap: ["error", { "capIsNewExceptionPattern": "^person\\.." }]*/ var friend = person.Acquaintance(); var bestFriend = person.Friend(); ``` +Examples of additional **correct** code for this rule with the `{ "capIsNewExceptionPattern": "\\.Bar$" }` option: + +```js +/*eslint new-cap: ["error", { "capIsNewExceptionPattern": "\\.Bar$" }]*/ + +foo.Bar(); +``` + +Examples of additional **correct** code for this rule with the `{ "capIsNewExceptionPattern": "^Foo" }` option: + +```js +/*eslint new-cap: ["error", { "capIsNewExceptionPattern": "^Foo" }]*/ + +var x = Foo(42); + +var y = Foobar(42); + +var z = Foo.Bar(42); +``` + ### properties Examples of **incorrect** code for this rule with the default `{ "properties": true }` option: diff --git a/eslint/docs/rules/no-console.md b/eslint/docs/rules/no-console.md index f2fb79e..b1e4e60 100644 --- a/eslint/docs/rules/no-console.md +++ b/eslint/docs/rules/no-console.md @@ -9,7 +9,7 @@ console.error("That shouldn't have happened."); ## Rule Details -This rule disallows calls to methods of the `console` object. +This rule disallows calls or assignments to methods of the `console` object. Examples of **incorrect** code for this rule: @@ -19,6 +19,7 @@ Examples of **incorrect** code for this rule: console.log("Log a debug level message."); console.warn("Log a warn level message."); console.error("Log an error level message."); +console.log = foo(); ``` Examples of **correct** code for this rule: diff --git a/eslint/docs/rules/no-dupe-class-members.md b/eslint/docs/rules/no-dupe-class-members.md index 9c1cc4f..48b22e8 100644 --- a/eslint/docs/rules/no-dupe-class-members.md +++ b/eslint/docs/rules/no-dupe-class-members.md @@ -25,7 +25,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-dupe-class-members: "error"*/ -/*eslint-env es6*/ class Foo { bar() { } @@ -37,6 +36,16 @@ class Foo { get bar() { } } +class Foo { + bar; + bar; +} + +class Foo { + bar; + bar() { } +} + class Foo { static bar() { } static bar() { } @@ -47,7 +56,6 @@ Examples of **correct** code for this rule: ```js /*eslint no-dupe-class-members: "error"*/ -/*eslint-env es6*/ class Foo { bar() { } @@ -59,6 +67,16 @@ class Foo { set bar(value) { } } +class Foo { + bar; + qux; +} + +class Foo { + bar; + qux() { } +} + class Foo { static bar() { } bar() { } diff --git a/eslint/docs/rules/no-eq-null.md b/eslint/docs/rules/no-eq-null.md index df8f97a..7b0f5df 100644 --- a/eslint/docs/rules/no-eq-null.md +++ b/eslint/docs/rules/no-eq-null.md @@ -39,3 +39,11 @@ while (qux !== null) { baz(); } ``` + +## Compatibility + +* **JSHint**: This rule corresponds to `eqnull` rule of JSHint. + +## When Not To Use It + +If you want to enforce type-checking operations in general, use the more powerful [eqeqeq](./eqeqeq) instead. diff --git a/eslint/docs/rules/no-eval.md b/eslint/docs/rules/no-eval.md index 9f7b71a..4a2a3b7 100644 --- a/eslint/docs/rules/no-eval.md +++ b/eslint/docs/rules/no-eval.md @@ -66,6 +66,14 @@ class A { eval() { } + + static { + // This is a user-defined static method. + this.eval("var a = 0"); + } + + static eval() { + } } ``` diff --git a/eslint/docs/rules/no-extra-parens.md b/eslint/docs/rules/no-extra-parens.md index 68b986e..b521dcb 100644 --- a/eslint/docs/rules/no-extra-parens.md +++ b/eslint/docs/rules/no-extra-parens.md @@ -48,6 +48,14 @@ for (a of (b)); typeof (a); (function(){} ? a() : b()); + +class A { + [(x)] = 1; +} + +class B { + x = (y + z); +} ``` Examples of **correct** code for this rule with the default `"all"` option: @@ -72,6 +80,14 @@ for (a of b); for (a in b, c); for (a in b); + +class A { + [x] = 1; +} + +class B { + x = y + z; +} ``` ### conditionalAssign diff --git a/eslint/docs/rules/no-extra-semi.md b/eslint/docs/rules/no-extra-semi.md index f4dd8e0..a33a493 100644 --- a/eslint/docs/rules/no-extra-semi.md +++ b/eslint/docs/rules/no-extra-semi.md @@ -17,6 +17,17 @@ function foo() { // code }; +class C { + field;; + + method() { + // code + }; + + static { + // code + }; +}; ``` Examples of **correct** code for this rule: @@ -26,10 +37,25 @@ Examples of **correct** code for this rule: var x = 5; -var foo = function() { +function foo() { + // code +} + +var bar = function() { // code }; +class C { + field; + + method() { + // code + } + + static { + // code + } +} ``` ## When Not To Use It diff --git a/eslint/docs/rules/no-fallthrough.md b/eslint/docs/rules/no-fallthrough.md index 15ca77f..81b428c 100644 --- a/eslint/docs/rules/no-fallthrough.md +++ b/eslint/docs/rules/no-fallthrough.md @@ -54,6 +54,17 @@ switch(foo) { case 2: doSomethingElse(); } + +switch(foo) { + case 1: { + doSomething(); + // falls through + } + + case 2: { + doSomethingElse(); + } +} ``` In this example, there is no confusion as to the expected behavior. It is clear that the first case is meant to fall through to the second case. @@ -124,6 +135,17 @@ switch(foo) { case 2: doSomething(); } + +switch(foo) { + case 1: { + doSomething(); + // falls through + } + + case 2: { + doSomethingElse(); + } +} ``` Note that the last `case` statement in these examples does not cause a warning because there is nothing to fall through into. diff --git a/eslint/docs/rules/no-implicit-globals.md b/eslint/docs/rules/no-implicit-globals.md index 04a3cd3..479d958 100644 --- a/eslint/docs/rules/no-implicit-globals.md +++ b/eslint/docs/rules/no-implicit-globals.md @@ -70,7 +70,7 @@ function bar() {} ### Global variable leaks When the code is not in `strict` mode, an assignment to an undeclared variable creates -a new global variable. This will happen even is the code is in a function. +a new global variable. This will happen even if the code is in a function. This does not apply to ES modules since the module code is implicitly in `strict` mode. diff --git a/eslint/docs/rules/no-import-assign.md b/eslint/docs/rules/no-import-assign.md index 78b228d..49595cd 100644 --- a/eslint/docs/rules/no-import-assign.md +++ b/eslint/docs/rules/no-import-assign.md @@ -16,8 +16,10 @@ import * as mod_ns from "./mod.mjs" mod = 1 // ERROR: 'mod' is readonly. named = 2 // ERROR: 'named' is readonly. -mod_ns.named = 3 // ERROR: the members of 'mod_ns' is readonly. +mod_ns.named = 3 // ERROR: The members of 'mod_ns' are readonly. mod_ns = {} // ERROR: 'mod_ns' is readonly. +// Can't extend 'mod_ns' +Object.assign(mod_ns, { foo: "foo" }) // ERROR: The members of 'mod_ns' are readonly. ``` Examples of **correct** code for this rule: diff --git a/eslint/docs/rules/no-inner-declarations.md b/eslint/docs/rules/no-inner-declarations.md index 707fc6e..7c64e9d 100644 --- a/eslint/docs/rules/no-inner-declarations.md +++ b/eslint/docs/rules/no-inner-declarations.md @@ -1,4 +1,4 @@ -# disallow variable or `function` declarations in nested blocks (no-inner-declarations) +# disallow variable or `function` declarations in nested blocks (no-inner-declarations) In JavaScript, prior to ES6, a function declaration is only allowed in the first level of a program or the body of another function, though parsers sometimes [erroneously accept them elsewhere](https://code.google.com/p/esprima/issues/detail?id=422). This only applies to function declarations; named or anonymous function expressions can occur anywhere an expression is permitted. @@ -56,7 +56,7 @@ function doSomething() { ## Rule Details -This rule requires that function declarations and, optionally, variable declarations be in the root of a program or the body of a function. +This rule requires that function declarations and, optionally, variable declarations be in the root of a program, or in the root of the body of a function, or in the root of the body of a class static block. ## Options @@ -83,6 +83,14 @@ function doSomethingElse() { } if (foo) function f(){} + +class C { + static { + if (test) { + function doSomething() { } + } + } +} ``` Examples of **correct** code for this rule with the default `"functions"` option: @@ -96,6 +104,12 @@ function doSomethingElse() { function doAnotherThing() { } } +class C { + static { + function doSomething() { } + } +} + if (test) { asyncCall(id, function (err, data) { }); } @@ -125,17 +139,23 @@ function doAnotherThing() { } } - if (foo) var a; if (foo) function f(){} + +class C { + static { + if (test) { + var something; + } + } +} ``` Examples of **correct** code for this rule with the `"both"` option: ```js /*eslint no-inner-declarations: ["error", "both"]*/ -/*eslint-env es6*/ var bar = 42; @@ -146,6 +166,12 @@ if (test) { function doAnotherThing() { var baz = 81; } + +class C { + static { + var something; + } +} ``` ## When Not To Use It diff --git a/eslint/docs/rules/no-invalid-this.md b/eslint/docs/rules/no-invalid-this.md index 7614b59..f2f17d4 100644 --- a/eslint/docs/rules/no-invalid-this.md +++ b/eslint/docs/rules/no-invalid-this.md @@ -18,7 +18,7 @@ This rule judges from following conditions whether or not the function is a meth * The function is on an object literal. * The function is assigned to a property. -* The function is a method/getter/setter of ES2015 Classes. (excepts static methods) +* The function is a method/getter/setter of ES2015 Classes. And this rule allows `this` keywords in functions below: @@ -26,6 +26,11 @@ And this rule allows `this` keywords in functions below: * The function is a callback of array methods (such as `.forEach()`) if `thisArg` is given. * The function has `@this` tag in its JSDoc comment. +And this rule always allows `this` keywords in the following contexts: + +* In class field initializers. +* In class static blocks. + Otherwise are considered problems. This rule applies **only** in strict mode. @@ -166,6 +171,13 @@ Foo.prototype.foo = function foo() { }; class Foo { + + // OK, this is in a class field initializer. + a = this.b; + + // OK, static initializers also have valid this. + static a = this.b; + foo() { // OK, this is in a method. this.a = 0; @@ -177,6 +189,12 @@ class Foo { this.a = 0; baz(() => this); } + + static { + // OK, static blocks also have valid this. + this.a = 0; + baz(() => this); + } } var foo = (function foo() { diff --git a/eslint/docs/rules/no-lone-blocks.md b/eslint/docs/rules/no-lone-blocks.md index 8cb45d6..49fdda6 100644 --- a/eslint/docs/rules/no-lone-blocks.md +++ b/eslint/docs/rules/no-lone-blocks.md @@ -42,6 +42,14 @@ function bar() { aLabel: { } } + +class C { + static { + { + foo(); + } + } +} ``` Examples of **correct** code for this rule with ES6 environment: @@ -78,6 +86,18 @@ function bar() { aLabel: { } + +class C { + static { + lbl: { + if (something) { + break lbl; + } + + foo(); + } + } +} ``` Examples of **correct** code for this rule with ES6 environment and strict mode via `"parserOptions": { "sourceType": "module" }` in the ESLint configuration or `"use strict"` directive in the code: diff --git a/eslint/docs/rules/no-multi-assign.md b/eslint/docs/rules/no-multi-assign.md index 7ef7056..14721da 100644 --- a/eslint/docs/rules/no-multi-assign.md +++ b/eslint/docs/rules/no-multi-assign.md @@ -26,12 +26,19 @@ const foo = bar = "baz"; let a = b = c; + +class Foo { + a = b = 10; +} + +a = b = "quux"; ``` Examples of **correct** code for this rule: ```js /*eslint no-multi-assign: "error"*/ + var a = 5; var b = 5; var c = 5; @@ -41,13 +48,21 @@ const bar = "baz"; let a = c; let b = c; + +class Foo { + a = 10; + b = 10; +} + +a = "quux"; +b = "quux"; ``` ## Options This rule has an object option: -* `"ignoreNonDeclaration"`: When set to `true`, the rule allows chains that don't include initializing a variable in a declaration. Default is `false`. +* `"ignoreNonDeclaration"`: When set to `true`, the rule allows chains that don't include initializing a variable in a declaration or initializing a class field. Default is `false`. ### ignoreNonDeclaration @@ -73,6 +88,10 @@ Examples of **incorrect** code for the `{ "ignoreNonDeclaration": true }` option let a = b = "baz"; const foo = bar = 1; + +class Foo { + a = b = 10; +} ``` ## Related Rules diff --git a/eslint/docs/rules/no-new-func.md b/eslint/docs/rules/no-new-func.md index 16ac00c..7bdfc9e 100644 --- a/eslint/docs/rules/no-new-func.md +++ b/eslint/docs/rules/no-new-func.md @@ -1,12 +1,16 @@ # Disallow Function Constructor (no-new-func) -It's possible to create functions in JavaScript using the `Function` constructor, such as: +It's possible to create functions in JavaScript from strings at runtime using the `Function` constructor, such as: ```js var x = new Function("a", "b", "return a + b"); +var x = Function("a", "b", "return a + b"); +var x = Function.call(null, "a", "b", "return a + b"); +var x = Function.apply(null, ["a", "b", "return a + b"]); +var x = Function.bind(null, "a", "b", "return a + b")(); ``` -This is considered by many to be a bad practice due to the difficulty in debugging and reading these types of functions. +This is considered by many to be a bad practice due to the difficulty in debugging and reading these types of functions. In addition, Content-Security-Policy (CSP) directives may disallow the use of eval() and similar methods for creating code from strings. ## Rule Details @@ -19,6 +23,10 @@ Examples of **incorrect** code for this rule: var x = new Function("a", "b", "return a + b"); var x = Function("a", "b", "return a + b"); +var x = Function.call(null, "a", "b", "return a + b"); +var x = Function.apply(null, ["a", "b", "return a + b"]); +var x = Function.bind(null, "a", "b", "return a + b")(); +var f = Function.bind(null, "a", "b", "return a + b"); // assuming that the result of Function.bind(...) will be eventually called. ``` Examples of **correct** code for this rule: diff --git a/eslint/docs/rules/no-redeclare.md b/eslint/docs/rules/no-redeclare.md index c3b8422..bd029eb 100644 --- a/eslint/docs/rules/no-redeclare.md +++ b/eslint/docs/rules/no-redeclare.md @@ -13,6 +13,18 @@ Examples of **incorrect** code for this rule: var a = 3; var a = 10; + +class C { + foo() { + var b = 3; + var b = 10; + } + + static { + var c = 3; + var c = 10; + } +} ``` Examples of **correct** code for this rule: @@ -21,8 +33,20 @@ Examples of **correct** code for this rule: /*eslint no-redeclare: "error"*/ var a = 3; -// ... a = 10; + +class C { + foo() { + var b = 3; + b = 10; + } + + static { + var c = 3; + c = 10; + } +} + ``` ## Options diff --git a/eslint/docs/rules/no-undef-init.md b/eslint/docs/rules/no-undef-init.md index 10db58c..0cf99c1 100644 --- a/eslint/docs/rules/no-undef-init.md +++ b/eslint/docs/rules/no-undef-init.md @@ -19,13 +19,12 @@ It's considered a best practice to avoid initializing variables to `undefined`. ## Rule Details -This rule aims to eliminate variable declarations that initialize to `undefined`. +This rule aims to eliminate `var` and `let` variable declarations that initialize to `undefined`. Examples of **incorrect** code for this rule: ```js /*eslint no-undef-init: "error"*/ -/*eslint-env es6*/ var foo = undefined; let bar = undefined; @@ -35,11 +34,29 @@ Examples of **correct** code for this rule: ```js /*eslint no-undef-init: "error"*/ -/*eslint-env es6*/ var foo; let bar; -const baz = undefined; +``` + +Please note that this rule does not check `const` declarations, destructuring patterns, function parameters, and class fields. + +Examples of additional **correct** code for this rule: + +```js +/*eslint no-undef-init: "error"*/ + +const foo = undefined; + +let { bar = undefined } = baz; + +[quux = undefined] = quuux; + +(foo = undefined) => {}; + +class Foo { + bar = undefined; +} ``` ## When Not To Use It diff --git a/eslint/docs/rules/no-unreachable.md b/eslint/docs/rules/no-unreachable.md index 7bd16d3..2fe8a69 100644 --- a/eslint/docs/rules/no-unreachable.md +++ b/eslint/docs/rules/no-unreachable.md @@ -10,9 +10,21 @@ function fn() { } ``` +Another kind of mistake is defining instance fields in a subclass whose constructor doesn't call `super()`. Instance fields of a subclass are only added to the instance after `super()`. If there are no `super()` calls, their definitions are never applied and therefore are unreachable code. + +```js +class C extends B { + #x; // this will never be added to instances + + constructor() { + return {}; + } +} +``` + ## Rule Details -This rule disallows unreachable code after `return`, `throw`, `continue`, and `break` statements. +This rule disallows unreachable code after `return`, `throw`, `continue`, and `break` statements. This rule also flags definitions of instance fields in subclasses whose constructors don't have `super()` calls. Examples of **incorrect** code for this rule: @@ -73,3 +85,57 @@ switch (foo) { var x; } ``` + +Examples of additional **incorrect** code for this rule: + +```js +/*eslint no-unreachable: "error"*/ + +class C extends B { + #x; // unreachable + #y = 1; // unreachable + a; // unreachable + b = 1; // unreachable + + constructor() { + return {}; + } +} +``` + +Examples of additional **correct** code for this rule: + +```js +/*eslint no-unreachable: "error"*/ + +class D extends B { + #x; + #y = 1; + a; + b = 1; + + constructor() { + super(); + } +} + +class E extends B { + #x; + #y = 1; + a; + b = 1; + + // implicit constructor always calls `super()` +} + +class F extends B { + static #x; + static #y = 1; + static a; + static b = 1; + + constructor() { + return {}; + } +} +``` diff --git a/eslint/docs/rules/no-unused-expressions.md b/eslint/docs/rules/no-unused-expressions.md index 1557725..89cfb33 100644 --- a/eslint/docs/rules/no-unused-expressions.md +++ b/eslint/docs/rules/no-unused-expressions.md @@ -62,16 +62,6 @@ injectGlobal`body{ color: red; }` ``` -Note that one or more string expression statements (with or without semi-colons) will only be considered as unused if they are not in the beginning of a script, module, or function (alone and uninterrupted by other statements). Otherwise, they will be treated as part of a "directive prologue", a section potentially usable by JavaScript engines. This includes "strict mode" directives. - -```js -"use strict"; -"use asm" -"use stricter"; -"use babel" -"any other strings like this in the prologue"; -``` - Examples of **correct** code for the default `{ "allowShortCircuit": false, "allowTernary": false }` options: ```js @@ -96,6 +86,50 @@ delete a.b void a ``` +Note that one or more string expression statements (with or without semi-colons) will only be considered as unused if they are not in the beginning of a script, module, or function (alone and uninterrupted by other statements). Otherwise, they will be treated as part of a "directive prologue", a section potentially usable by JavaScript engines. This includes "strict mode" directives. + +Examples of **correct** code for this rule in regard to directives: + +```js +/*eslint no-unused-expressions: "error"*/ + +"use strict"; +"use asm" +"use stricter"; +"use babel" +"any other strings like this in the directive prologue"; +"this is still the directive prologue"; + +function foo() { + "bar"; +} + +class Foo { + someMethod() { + "use strict"; + } +} +``` + +Examples of **incorrect** code for this rule in regard to directives: + +```js +/*eslint no-unused-expressions: "error"*/ + +doSomething(); +"use strict"; // this isn't in a directive prologue, because there is a non-directive statement before it + +function foo() { + "bar" + 1; +} + +class Foo { + static { + "use strict"; // class static blocks do not have directive prologues + } +} +``` + ### allowShortCircuit Examples of **incorrect** code for the `{ "allowShortCircuit": true }` option: diff --git a/eslint/docs/rules/no-unused-private-class-members.md b/eslint/docs/rules/no-unused-private-class-members.md new file mode 100644 index 0000000..2eb9f4b --- /dev/null +++ b/eslint/docs/rules/no-unused-private-class-members.md @@ -0,0 +1,78 @@ +# Disallow Unused Private Class Members (no-unused-private-class-members) + +Private class members that are declared and not used anywhere in the code are most likely an error due to incomplete refactoring. Such class members take up space in the code and can lead to confusion by readers. + +## Rule Details + +This rule reports unused private class members. + +* A private field or method is considered to be unused if its value is never read. +* A private accessor is considered to be unused if it is never accessed (read or write). + +Examples of **incorrect** code for this rule: + +```js +/*eslint no-unused-private-class-members: "error"*/ + +class Foo { + #unusedMember = 5; +} + +class Foo { + #usedOnlyInWrite = 5; + method() { + this.#usedOnlyInWrite = 42; + } +} + +class Foo { + #usedOnlyToUpdateItself = 5; + method() { + this.#usedOnlyToUpdateItself++; + } +} + +class Foo { + #unusedMethod() {} +} + +class Foo { + get #unusedAccessor() {} + set #unusedAccessor(value) {} +} +``` + +Examples of **correct** code for this rule: + +```js +/*eslint no-unused-private-class-members: "error"*/ + +class Foo { + #usedMember = 42; + method() { + return this.#usedMember; + } +} + +class Foo { + #usedMethod() { + return 42; + } + anotherMethod() { + return this.#usedMethod(); + } +} + +class Foo { + get #usedAccessor() {} + set #usedAccessor(value) {} + + method() { + this.#usedAccessor = 42; + } +} +``` + +## When Not To Use It + +If you don't want to be notified about unused private class members, you can safely turn this rule off. diff --git a/eslint/docs/rules/no-use-before-define.md b/eslint/docs/rules/no-use-before-define.md index 391c323..8af260e 100644 --- a/eslint/docs/rules/no-use-before-define.md +++ b/eslint/docs/rules/no-use-before-define.md @@ -12,7 +12,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-use-before-define: "error"*/ -/*eslint-env es6*/ alert(a); var a = 10; @@ -29,13 +28,37 @@ var b = 1; alert(c); let c = 1; } + +{ + class C extends C {} +} + +{ + class C { + static x = "foo"; + [C.x]() {} + } +} + +{ + const C = class { + static x = C; + } +} + +{ + const C = class { + static { + C.x = "foo"; + } + } +} ``` Examples of **correct** code for this rule: ```js /*eslint no-use-before-define: "error"*/ -/*eslint-env es6*/ var a; a = 10; @@ -53,13 +76,39 @@ function g() { let c; c++; } + +{ + class C { + static x = C; + } +} + +{ + const C = class C { + static x = C; + } +} + +{ + const C = class { + x = C; + } +} + +{ + const C = class C { + static { + C.x = "foo"; + } + } +} ``` ## Options ```json { - "no-use-before-define": ["error", { "functions": true, "classes": true }] + "no-use-before-define": ["error", { "functions": true, "classes": true, "variables": true }] } ``` @@ -103,18 +152,41 @@ Examples of **incorrect** code for the `{ "classes": false }` option: ```js /*eslint no-use-before-define: ["error", { "classes": false }]*/ -/*eslint-env es6*/ new A(); class A { } + +{ + class C extends C {} +} + +{ + class C extends D {} + class D {} +} + +{ + class C { + static x = "foo"; + [C.x]() {} + } +} + +{ + class C { + static { + new D(); + } + } + class D {} +} ``` Examples of **correct** code for the `{ "classes": false }` option: ```js /*eslint no-use-before-define: ["error", { "classes": false }]*/ -/*eslint-env es6*/ function foo() { return new A(); @@ -139,6 +211,28 @@ const f = () => {}; g(); const g = function() {}; + +{ + const C = class { + static x = C; + } +} + +{ + const C = class { + static x = foo; + } + const foo = 1; +} + +{ + class C { + static { + this.x = foo; + } + } + const foo = 1; +} ``` Examples of **correct** code for the `{ "variables": false }` option: @@ -158,4 +252,11 @@ const f = () => {}; const e = function() { return g(); } const g = function() {} + +{ + const C = class { + x = foo; + } + const foo = 1; +} ``` diff --git a/eslint/docs/rules/no-useless-computed-key.md b/eslint/docs/rules/no-useless-computed-key.md index 9b3c592..2d3ddb3 100644 --- a/eslint/docs/rules/no-useless-computed-key.md +++ b/eslint/docs/rules/no-useless-computed-key.md @@ -20,7 +20,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-useless-computed-key: "error"*/ -/*eslint-env es6*/ var a = { ['0']: 0 }; var a = { ['0+1,234']: 0 }; @@ -41,6 +40,18 @@ var c = { a: 0 }; var c = { '0+1,234': 0 }; ``` +Examples of additional **correct** code for this rule: + +```js +/*eslint no-useless-computed-key: "error"*/ + +var c = { + "__proto__": foo, // defines object's prototype + + ["__proto__"]: bar // defines a property named "__proto__" +}; +``` + ## Options This rule has an object option: @@ -52,24 +63,64 @@ This rule has an object option: By default, this rule does not check class declarations and class expressions, as the default value for `enforceForClassMembers` is `false`. -When `enforceForClassMembers` is set to `true`, the rule will also disallow unnecessary computed -keys inside of class methods, getters and setters. +When `enforceForClassMembers` is set to `true`, the rule will also disallow unnecessary computed keys inside of class fields, class methods, class getters, and class setters. -Examples of **incorrect** code for `{ "enforceForClassMembers": true }`: +Examples of **incorrect** code for this rule with the `{ "enforceForClassMembers": true }` option: ```js /*eslint no-useless-computed-key: ["error", { "enforceForClassMembers": true }]*/ class Foo { + ["foo"] = "bar"; + [0]() {} ['a']() {} get ['b']() {} set ['c'](value) {} + static ["foo"] = "bar"; + static ['a']() {} } ``` +Examples of **correct** code for this rule with the `{ "enforceForClassMembers": true }` option: + +```js +/*eslint no-useless-computed-key: ["error", { "enforceForClassMembers": true }]*/ + +class Foo { + "foo" = "bar"; + + 0() {} + 'a'() {} + get 'b'() {} + set 'c'(value) {} + + static "foo" = "bar"; + + static 'a'() {} +} +``` + +Examples of additional **correct** code for this rule with the `{ "enforceForClassMembers": true }` option: + +```js +/*eslint no-useless-computed-key: ["error", { "enforceForClassMembers": true }]*/ + +class Foo { + ["constructor"]; // instance field named "constructor" + + "constructor"() {} // the constructor of this class + + ["constructor"]() {} // method named "constructor" + + static ["constructor"]; // static field named "constructor" + + static ["prototype"]; // runtime error, it would be a parsing error without `[]` +} +``` + ## When Not To Use It If you don't want to be notified about unnecessary computed property keys, you can safely disable this rule. diff --git a/eslint/docs/rules/one-var.md b/eslint/docs/rules/one-var.md index 3c964c4..df7ee1f 100644 --- a/eslint/docs/rules/one-var.md +++ b/eslint/docs/rules/one-var.md @@ -66,7 +66,6 @@ Examples of **incorrect** code for this rule with the default `"always"` option: ```js /*eslint one-var: ["error", "always"]*/ -/*eslint-env es6*/ function foo() { var bar; @@ -89,13 +88,31 @@ function foo() { var qux = true; } } + +class C { + static { + var foo; + var bar; + } + + static { + var foo; + if (bar) { + var baz = true; + } + } + + static { + let foo; + let bar; + } +} ``` Examples of **correct** code for this rule with the default `"always"` option: ```js /*eslint one-var: ["error", "always"]*/ -/*eslint-env es6*/ function foo() { var bar, @@ -127,6 +144,30 @@ function foo(){ let qux; } } + +class C { + static { + var foo, bar; + } + + static { + var foo, baz; + if (bar) { + baz = true; + } + } + + static { + let foo, bar; + } + + static { + let foo; + if (bar) { + let baz; + } + } +} ``` ### never @@ -135,7 +176,6 @@ Examples of **incorrect** code for this rule with the `"never"` option: ```js /*eslint one-var: ["error", "never"]*/ -/*eslint-env es6*/ function foo() { var bar, @@ -157,13 +197,19 @@ function foo(){ let bar = true, baz = false; } + +class C { + static { + var foo, bar; + let baz, qux; + } +} ``` Examples of **correct** code for this rule with the `"never"` option: ```js /*eslint one-var: ["error", "never"]*/ -/*eslint-env es6*/ function foo() { var bar; @@ -185,6 +231,15 @@ function foo() { let qux = true; } } + +class C { + static { + var foo; + var bar; + let baz; + let qux; + } +} ``` ### consecutive @@ -193,7 +248,6 @@ Examples of **incorrect** code for this rule with the `"consecutive"` option: ```js /*eslint one-var: ["error", "consecutive"]*/ -/*eslint-env es6*/ function foo() { var bar; @@ -209,14 +263,21 @@ function foo(){ var qux = 3; var quux; } + +class C { + static { + var foo; + var bar; + let baz; + let qux; + } +} ``` Examples of **correct** code for this rule with the `"consecutive"` option: ```js /*eslint one-var: ["error", "consecutive"]*/ -/*eslint-env es6*/ - function foo() { var bar, @@ -232,6 +293,16 @@ function foo(){ var qux = 3, quux; } + +class C { + static { + var foo, bar; + let baz, qux; + doSomething(); + let quux; + var quuux; + } +} ``` ### var, let, and const diff --git a/eslint/docs/rules/operator-linebreak.md b/eslint/docs/rules/operator-linebreak.md index 159bd52..6ae484e 100644 --- a/eslint/docs/rules/operator-linebreak.md +++ b/eslint/docs/rules/operator-linebreak.md @@ -60,6 +60,16 @@ if (someCondition answer = everything ? 42 : foo; + +class Foo { + a + = 1; + [b] + = 2; + [c + ] + = 3; +} ``` Examples of **correct** code for this rule with the `"after"` option: @@ -82,6 +92,17 @@ if (someCondition || answer = everything ? 42 : foo; + +class Foo { + a = + 1; + [b] = + 2; + [c + ] = + 3; + d = 4; +} ``` ### before @@ -104,6 +125,16 @@ if (someCondition || answer = everything ? 42 : foo; + +class Foo { + a = + 1; + [b] = + 2; + [c + ] = + 3; +} ``` Examples of **correct** code for this rule with the `"before"` option: @@ -126,6 +157,17 @@ if (someCondition answer = everything ? 42 : foo; + +class Foo { + a + = 1; + [b] + = 2; + [c + ] + = 3; + d = 4; +} ``` ### none @@ -156,6 +198,23 @@ answer = everything answer = everything ? 42 : foo; + +class Foo { + a = + 1; + [b] = + 2; + [c + ] = + 3; + d + = 4; + [e] + = 5; + [f + ] + = 6; +} ``` Examples of **correct** code for this rule with the `"none"` option: @@ -171,6 +230,17 @@ if (someCondition || otherCondition) { } answer = everything ? 42 : foo; + +class Foo { + a = 1; + [b] = 2; + [c + ] = 3; + d = 4; + [e] = 5; + [f + ] = 6; +} ``` ### overrides diff --git a/eslint/docs/rules/padded-blocks.md b/eslint/docs/rules/padded-blocks.md index d8b855e..85c0a8b 100644 --- a/eslint/docs/rules/padded-blocks.md +++ b/eslint/docs/rules/padded-blocks.md @@ -27,12 +27,12 @@ The second one is an object option, it can allow exceptions. String option: -* `"always"` (default) requires empty lines at the beginning and ending of block statements and classes -* `"never"` disallows empty lines at the beginning and ending of block statements and classes +* `"always"` (default) requires empty lines at the beginning and ending of block statements, function bodies, class static blocks, classes, and `switch` statements. +* `"never"` disallows empty lines at the beginning and ending of block statements, function bodies, class static blocks, classes, and `switch` statements. Object option: -* `"blocks"` require or disallow padding within block statements +* `"blocks"` require or disallow padding within block statements, function bodies, and class static blocks * `"classes"` require or disallow padding within classes * `"switches"` require or disallow padding within `switch` statements @@ -68,6 +68,12 @@ if (a) { b(); } + +class C { + static { + a(); + } +} ``` Examples of **correct** code for this rule with the default `"always"` option: @@ -93,6 +99,16 @@ if (a) { // comment b(); +} + +class C { + + static { + + a(); + + } + } ``` @@ -124,6 +140,16 @@ if (a) { if (a) { b(); +} + +class C { + + static { + + a(); + + } + } ``` @@ -140,6 +166,12 @@ if (a) { b(); } + +class C { + static { + a(); + } +} ``` ### blocks @@ -174,6 +206,14 @@ if (a) { // comment b(); +} + +class C { + + static { + a(); + } + } ``` @@ -200,6 +240,25 @@ if (a) { // comment b(); +} + +class C { + + static { + + a(); + + } + +} + +class D { + static { + + a(); + + } + } ``` @@ -230,6 +289,14 @@ if (a) { b(); } + +class C { + static { + + a(); + + } +} ``` Examples of **correct** code for this rule with the `{ "blocks": "never" }` option: @@ -245,6 +312,20 @@ if (a) { b(); } + +class C { + static { + a(); + } +} + +class D { + + static { + a(); + } + +} ``` ### classes diff --git a/eslint/docs/rules/padding-line-between-statements.md b/eslint/docs/rules/padding-line-between-statements.md index 2f2f67e..065c7d9 100644 --- a/eslint/docs/rules/padding-line-between-statements.md +++ b/eslint/docs/rules/padding-line-between-statements.md @@ -147,6 +147,13 @@ function foo() { const a = 0; bar(); } + +class C { + static { + let a = 0; + bar(); + } +} ``` Examples of **correct** code for the `[{ blankLine: "always", prev: ["const", "let", "var"], next: "*"}, { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"]}]` configuration: @@ -178,6 +185,15 @@ function foo() { bar(); } + +class C { + static { + let a = 0; + let b = 0; + + bar(); + } +} ``` ---- diff --git a/eslint/docs/rules/prefer-const.md b/eslint/docs/rules/prefer-const.md index 3e5a5d0..3cdba9e 100644 --- a/eslint/docs/rules/prefer-const.md +++ b/eslint/docs/rules/prefer-const.md @@ -12,7 +12,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint prefer-const: "error"*/ -/*eslint-env es6*/ // it's initialized and never reassigned. let a = 3; @@ -22,6 +21,14 @@ let a; a = 0; console.log(a); +class C { + static { + let a; + a = 0; + console.log(a); + } +} + // `i` is redefined (not reassigned) on each loop step. for (let i in [1, 2, 3]) { console.log(i); @@ -37,7 +44,6 @@ Examples of **correct** code for this rule: ```js /*eslint prefer-const: "error"*/ -/*eslint-env es6*/ // using const. const a = 0; @@ -59,6 +65,15 @@ if (true) { } console.log(a); +// it's initialized in a different scope. +let a; +class C { + #x; + static { + a = obj => obj.#x; + } +} + // it's initialized at a place that we cannot write a variable declaration. let a; if (true) a = 0; diff --git a/eslint/docs/rules/prefer-destructuring.md b/eslint/docs/rules/prefer-destructuring.md index 7f18559..64e914f 100644 --- a/eslint/docs/rules/prefer-destructuring.md +++ b/eslint/docs/rules/prefer-destructuring.md @@ -62,6 +62,17 @@ Examples of **correct** code when `enforceForRenamedProperties` is enabled: var { bar: foo } = object; ``` +Examples of additional **correct** code when `enforceForRenamedProperties` is enabled: + +```javascript +class C { + #x; + foo() { + const bar = this.#x; // private identifiers are not allowed in destructuring + } +} +``` + An example configuration, with the defaults `array` and `object` filled in, looks like this: ```json diff --git a/eslint/docs/rules/prefer-named-capture-group.md b/eslint/docs/rules/prefer-named-capture-group.md index ff8790c..2caf951 100644 --- a/eslint/docs/rules/prefer-named-capture-group.md +++ b/eslint/docs/rules/prefer-named-capture-group.md @@ -1,14 +1,20 @@ # Suggest using named capture group in regular expression (prefer-named-capture-group) + +## Rule Details + With the landing of ECMAScript 2018, named capture groups can be used in regular expressions, which can improve their readability. +This rule is aimed at using named capture groups instead of numbered capture groups in regular expressions: ```js const regex = /(?[0-9]{4})/; ``` -## Rule Details +Alternatively, if your intention is not to _capture_ the results, but only express the alternative, use a non-capturing group: -This rule is aimed at using named capture groups instead of numbered capture groups in regular expressions. +```js +const regex = /(?:cauli|sun)flower/; +``` Examples of **incorrect** code for this rule: @@ -30,13 +36,14 @@ Examples of **correct** code for this rule: const foo = /(?ba[rz])/; const bar = new RegExp('(?ba[rz])'); const baz = RegExp('(?ba[rz])'); +const xyz = /xyz(?:zy|abc)/; foo.exec('bar').groups.id; // Retrieve the group result. ``` ## When Not To Use It -If you are targeting ECMAScript 2017 and/or older environments, you can disable this rule, because this ECMAScript feature is only supported in ECMAScript 2018 and/or newer environments. +If you are targeting ECMAScript 2017 and/or older environments, you should not use this rule, because this ECMAScript feature is only supported in ECMAScript 2018 and/or newer environments. ## Related Rules diff --git a/eslint/docs/rules/require-atomic-updates.md b/eslint/docs/rules/require-atomic-updates.md index 8dd3138..64fae21 100644 --- a/eslint/docs/rules/require-atomic-updates.md +++ b/eslint/docs/rules/require-atomic-updates.md @@ -38,11 +38,22 @@ Promise.all([getPageLength(1), getPageLength(2)]).then(pageLengths => { ## Rule Details -This rule aims to report assignments to variables or properties where all of the following are true: +This rule aims to report assignments to variables or properties in cases where the assignments may be based on outdated values. -* A variable or property is reassigned to a new value which is based on its old value. -* A `yield` or `await` expression interrupts the assignment after the old value is read, and before the new value is set. -* The rule cannot easily verify that the assignment is safe (e.g. if an assigned variable is local and would not be readable from anywhere else while the function is paused). +### Variables + +This rule reports an assignment to a variable when it detects the following execution flow in a generator or async function: + +1. The variable is read. +2. A `yield` or `await` pauses the function. +3. After the function is resumed, a value is assigned to the variable from step 1. + +The assignment in step 3 is reported because it may be incorrectly resolved because the value of the variable from step 1 may have changed between steps 2 and 3. In particular, if the variable can be accessed from other execution contexts (for example, if it is not a local variable and therefore other functions can change it), the value of the variable may have changed elsewhere while the function was paused in step 2. + +Note that the rule does not report the assignment in step 3 in any of the following cases: + +* If the variable is read again between steps 2 and 3. +* If the variable cannot be accessed while the function is paused (for example, if it's a local variable). Examples of **incorrect** code for this rule: @@ -50,20 +61,27 @@ Examples of **incorrect** code for this rule: /* eslint require-atomic-updates: error */ let result; -async function foo() { - result += await somethingElse; - result = result + await somethingElse; +async function foo() { + result += await something; +} - result = result + doSomething(await somethingElse); +async function bar() { + result = result + await something; } -function* bar() { - result += yield; +async function baz() { + result = result + doSomething(await somethingElse); +} - result = result + (yield somethingElse); +async function qux() { + if (!result) { + result = await initialize(); + } +} - result = result + doSomething(yield somethingElse); +function* generator() { + result += yield; } ``` @@ -73,22 +91,89 @@ Examples of **correct** code for this rule: /* eslint require-atomic-updates: error */ let result; -async function foo() { - result = await somethingElse + result; - let tmp = await somethingElse; - result += tmp; +async function foobar() { + result = await something + result; +} + +async function baz() { + const tmp = doSomething(await somethingElse); + result += tmp; +} + +async function qux() { + if (!result) { + const tmp = await initialize(); + if (!result) { + result = tmp; + } + } +} + +async function quux() { + let localVariable = 0; + localVariable += await something; +} + +function* generator() { + result = (yield) + result; +} +``` + +### Properties + +This rule reports an assignment to a property through a variable when it detects the following execution flow in a generator or async function: + +1. The variable or object property is read. +2. A `yield` or `await` pauses the function. +3. After the function is resumed, a value is assigned to a property. - let localVariable = 0; - localVariable += await somethingElse; +This logic is similar to the logic for variables, but stricter because the property in step 3 doesn't have to be the same as the property in step 1. It is assumed that the flow depends on the state of the object as a whole. + +Example of **incorrect** code for this rule: + +```js +/* eslint require-atomic-updates: error */ + +async function foo(obj) { + if (!obj.done) { + obj.something = await getSomething(); + } } +``` -function* bar() { - result = (yield) + result; +Example of **correct** code for this rule: - result = (yield somethingElse) + result; +```js +/* eslint require-atomic-updates: error */ + +async function foo(obj) { + if (!obj.done) { + const tmp = await getSomething(); + if (!obj.done) { + obj.something = tmp; + } + } +} +``` + +## Options + +This rule has an object option: + +* `"allowProperties"`: When set to `true`, the rule does not report assignments to properties. Default is `false`. + +### allowProperties + +Example of **correct** code for this rule with the `{ "allowProperties": true }` option: + +```js +/* eslint require-atomic-updates: ["error", { "allowProperties": true }] */ - result = doSomething(yield somethingElse, result); +async function foo(obj) { + if (!obj.done) { + obj.something = await getSomething(); + } } ``` diff --git a/eslint/docs/rules/semi-style.md b/eslint/docs/rules/semi-style.md index 7bd916f..368bb53 100644 --- a/eslint/docs/rules/semi-style.md +++ b/eslint/docs/rules/semi-style.md @@ -32,6 +32,13 @@ for ( ) { foo() } + +class C { + static { + foo() + ;bar() + } +} ``` Examples of **correct** code for this rule with `"last"` option: @@ -49,6 +56,13 @@ for ( ) { foo() } + +class C { + static { + foo(); + bar() + } +} ``` Examples of **incorrect** code for this rule with `"first"` option: @@ -66,6 +80,13 @@ for ( ) { foo() } + +class C { + static { + foo(); + bar() + } +} ``` Examples of **correct** code for this rule with `"first"` option: @@ -83,6 +104,13 @@ for ( ) { foo() } + +class C { + static { + foo() + ;bar() + } +} ``` ## When Not To Use It diff --git a/eslint/docs/rules/semi.md b/eslint/docs/rules/semi.md index 049ae41..cb8e6dd 100644 --- a/eslint/docs/rules/semi.md +++ b/eslint/docs/rules/semi.md @@ -76,6 +76,8 @@ Object option (when `"never"`): * `"beforeStatementContinuationChars": "always"` requires semicolons at the end of statements if the next line starts with `[`, `(`, `/`, `+`, or `-`. * `"beforeStatementContinuationChars": "never"` disallows semicolons as the end of statements if it doesn't make ASI hazard even if the next line starts with `[`, `(`, `/`, `+`, or `-`. +**Note:** `beforeStatementContinuationChars` does not apply to class fields because class fields are not statements. + ### always Examples of **incorrect** code for this rule with the default `"always"` option: @@ -88,6 +90,10 @@ var name = "ESLint" object.method = function() { // ... } + +class Foo { + bar = 1 +} ``` Examples of **correct** code for this rule with the default `"always"` option: @@ -100,6 +106,10 @@ var name = "ESLint"; object.method = function() { // ... }; + +class Foo { + bar = 1; +} ``` ### never @@ -114,6 +124,10 @@ var name = "ESLint"; object.method = function() { // ... }; + +class Foo { + bar = 1; +} ``` Examples of **correct** code for this rule with the `"never"` option: @@ -142,6 +156,10 @@ import b from "b" ;(function() { // ... })() + +class Foo { + bar = 1 +} ``` #### omitLastInOneLineBlock @@ -154,6 +172,14 @@ Examples of additional **correct** code for this rule with the `"always", { "omi if (foo) { bar() } if (foo) { bar(); baz() } + +function f() { bar(); baz() } + +class C { + foo() { bar(); baz() } + + static { bar(); baz() } +} ``` #### beforeStatementContinuationChars diff --git a/eslint/docs/rules/space-after-keywords.md b/eslint/docs/rules/space-after-keywords.md index af1b739..7f6182b 100644 --- a/eslint/docs/rules/space-after-keywords.md +++ b/eslint/docs/rules/space-after-keywords.md @@ -2,7 +2,7 @@ (removed) This rule was **removed** in ESLint v2.0 and replaced by the [keyword-spacing](keyword-spacing.md) rule. -(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fix) automatically fixed problems reported by this rule. +(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#--fix) automatically fixed problems reported by this rule. Some style guides will require or disallow spaces following the certain keywords. diff --git a/eslint/docs/rules/space-before-blocks.md b/eslint/docs/rules/space-before-blocks.md index 25b81ca..35a4c16 100644 --- a/eslint/docs/rules/space-before-blocks.md +++ b/eslint/docs/rules/space-before-blocks.md @@ -11,6 +11,7 @@ This rule will enforce consistency of spacing before blocks. It is only applied * This rule ignores spacing which is between `=>` and a block. The spacing is handled by the `arrow-spacing` rule. * This rule ignores spacing which is between a keyword and a block. The spacing is handled by the `keyword-spacing` rule. +* This rule ignores spacing which is between `:` of a switch case and a block. The spacing is handled by the `switch-colon-spacing` rule. ## Options @@ -62,6 +63,9 @@ if (a) { c(); } +class C { + static{} /*no error. this is checked by `keyword-spacing` rule.*/ +} function a() {} @@ -210,4 +214,5 @@ You can turn this rule off if you are not concerned with the consistency of spac * [keyword-spacing](keyword-spacing.md) * [arrow-spacing](arrow-spacing.md) +* [switch-colon-spacing](switch-colon-spacing.md) * [brace-style](brace-style.md) diff --git a/eslint/docs/rules/space-before-keywords.md b/eslint/docs/rules/space-before-keywords.md index 8adf593..077c9a3 100644 --- a/eslint/docs/rules/space-before-keywords.md +++ b/eslint/docs/rules/space-before-keywords.md @@ -2,7 +2,7 @@ (removed) This rule was **removed** in ESLint v2.0 and **replaced** by the [keyword-spacing](keyword-spacing.md) rule. -(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fix) automatically fixed problems reported by this rule. +(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#--fix) automatically fixed problems reported by this rule. Keywords are syntax elements of JavaScript, such as `function` and `if`. These identifiers have special meaning to the language and so often appear in a different color in code editors. As an important part of the language, style guides often refer to the spacing that should be used around keywords. For example, you might have a style guide that says keywords should be always be preceded by spaces, which would mean `if-else` statements must look like this: diff --git a/eslint/docs/rules/space-return-throw-case.md b/eslint/docs/rules/space-return-throw-case.md index 024eb3c..ff55f7f 100644 --- a/eslint/docs/rules/space-return-throw-case.md +++ b/eslint/docs/rules/space-return-throw-case.md @@ -2,7 +2,7 @@ (removed) This rule was **removed** in ESLint v2.0 and **replaced** by the [keyword-spacing](keyword-spacing.md) rule. -(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fix) automatically fixed problems reported by this rule. +(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#--fix) automatically fixed problems reported by this rule. Require spaces following `return`, `throw`, and `case`. diff --git a/eslint/docs/rules/strict.md b/eslint/docs/rules/strict.md index a6d87c2..97285af 100644 --- a/eslint/docs/rules/strict.md +++ b/eslint/docs/rules/strict.md @@ -49,6 +49,8 @@ This rule disallows strict mode directives, no matter which option is specified, This rule disallows strict mode directives, no matter which option is specified, in functions with non-simple parameter lists (for example, parameter lists with default parameter values) because that is a syntax error in **ECMAScript 2016** and later. See the examples of the [function](#function) option. +This rule does not apply to class static blocks, no matter which option is specified, because class static blocks do not have directives. Therefore, a `"use strict"` statement in a class static block is not a directive, and will be reported by the [no-unused-expressions](no-unused-expressions.md) rule. + The `--fix` option on the command line does not insert new `"use strict"` statements, but only removes unneeded statements. ## Options diff --git a/eslint/docs/rules/use-isnan.md b/eslint/docs/rules/use-isnan.md index ae9fefe..67c83c9 100644 --- a/eslint/docs/rules/use-isnan.md +++ b/eslint/docs/rules/use-isnan.md @@ -25,6 +25,14 @@ if (foo == NaN) { if (foo != NaN) { // ... } + +if (foo == Number.NaN) { + // ... +} + +if (foo != Number.NaN) { + // ... +} ``` Examples of **correct** code for this rule: @@ -77,6 +85,26 @@ switch (NaN) { break; // ... } + +switch (foo) { + case Number.NaN: + bar(); + break; + case 1: + baz(); + break; + // ... +} + +switch (Number.NaN) { + case a: + bar(); + break; + case b: + baz(); + break; + // ... +} ``` Examples of **correct** code for this rule with `"enforceForSwitchCase"` option set to `true` (default): @@ -126,6 +154,26 @@ switch (NaN) { break; // ... } + +switch (foo) { + case Number.NaN: + bar(); + break; + case 1: + baz(); + break; + // ... +} + +switch (Number.NaN) { + case a: + bar(); + break; + case b: + baz(); + break; + // ... +} ``` ### enforceForIndexOf diff --git a/eslint/docs/rules/vars-on-top.md b/eslint/docs/rules/vars-on-top.md index 78a8bee..c8ce81e 100644 --- a/eslint/docs/rules/vars-on-top.md +++ b/eslint/docs/rules/vars-on-top.md @@ -14,11 +14,10 @@ Examples of **incorrect** code for this rule: ```js /*eslint vars-on-top: "error"*/ -// Variable declarations in a block: +// Variable declaration in a nested block, and a variable declaration after other statements: function doSomething() { - var first; if (true) { - first = true; + var first = true; } var second; } @@ -32,11 +31,34 @@ function doSomething() { ```js /*eslint vars-on-top: "error"*/ -// Variables after other statements: +// Variable declaration after other statements: f(); var a; ``` +```js +/*eslint vars-on-top: "error"*/ + +// Variables in class static blocks should be at the top of the static blocks. + +class C { + + // Variable declaration in a nested block: + static { + if (something) { + var a = true; + } + } + + // Variable declaration after other statements: + static { + f(); + var a; + } + +} +``` + Examples of **correct** code for this rule: ```js @@ -66,6 +88,26 @@ f(); ```js /*eslint vars-on-top: "error"*/ +class C { + + static { + var a; + if (something) { + a = true; + } + } + + static { + var a; + f(); + } + +} +``` + +```js +/*eslint vars-on-top: "error"*/ + // Directives may precede variable declarations. "use strict"; var a; diff --git a/eslint/docs/user-guide/command-line-interface.md b/eslint/docs/user-guide/command-line-interface.md index 99fd61e..ad84c37 100644 --- a/eslint/docs/user-guide/command-line-interface.md +++ b/eslint/docs/user-guide/command-line-interface.md @@ -47,7 +47,7 @@ Specifying rules and plugins: Fixing problems: --fix Automatically fix problems --fix-dry-run Automatically fix problems without saving the changes to the file system - --fix-type Array Specify the types of fixes to apply (problem, suggestion, layout) + --fix-type Array Specify the types of fixes to apply (directive, problem, suggestion, layout) Ignoring files: --ignore-path path::String Specify path of ignore file @@ -81,6 +81,7 @@ Miscellaneous: --init Run config initialization wizard - default: false --env-info Output execution environment information - default: false --no-error-on-unmatched-pattern Prevent errors when pattern is unmatched - default: false + --exit-on-fatal-error Exit with exit code 2 in case of fatal error - default: false --debug Output debugging information -h, --help Show help -v, --version Output the version number @@ -210,9 +211,9 @@ If the rule is defined within a plugin, you have to prefix the rule ID with the Examples: - eslint --rule 'quotes: [2, double]' - eslint --rule 'guard-for-in: 2' --rule 'brace-style: [2, 1tbs]' - eslint --rule 'jquery/dollar-sign: 2' + eslint --rule 'quotes: [error, double]' + eslint --rule 'guard-for-in: error' --rule 'brace-style: [error, 1tbs]' + eslint --rule 'jquery/dollar-sign: error' ### Fixing problems @@ -239,11 +240,12 @@ This flag can be useful for integrations (e.g. editor plugins) which need to aut #### `--fix-type` -This option allows you to specify the type of fixes to apply when using either `--fix` or `--fix-dry-run`. The three types of fixes are: +This option allows you to specify the type of fixes to apply when using either `--fix` or `--fix-dry-run`. The four types of fixes are: 1. `problem` - fix potential errors in the code 1. `suggestion` - apply fixes to the code that improve it 1. `layout` - apply fixes that do not change the program structure (AST) +1. `directive` - apply fixes to inline directives such as `// eslint-disable` You can specify one or more fix type on the command line. Here are some examples: @@ -337,14 +339,12 @@ When specified, the given format is output into the provided file name. This option specifies the output format for the console. Possible formats are: * [checkstyle](formatters.md/#checkstyle) -* [codeframe](formatters.md/#codeframe) * [compact](formatters.md/#compact) * [html](formatters.md/#html) * [jslint-xml](formatters.md/#jslint-xml) * [json](formatters.md/#json) * [junit](formatters.md/#junit) * [stylish](formatters.md/#stylish) (the default) -* [table](formatters.md/#table) * [tap](formatters.md/#tap) * [unix](formatters.md/#unix) * [visualstudio](formatters.md/#visualstudio) @@ -467,9 +467,14 @@ This option outputs information about the execution environment, including the v This option prevents errors when a quoted glob pattern or `--ext` is unmatched. This will not prevent errors when your shell can't match a glob. +#### `--exit-on-fatal-error` + +This option causes ESLint to exit with exit code 2 if one or more fatal parsing errors occur. Without this option, fatal parsing errors are reported as rule violations. + #### `--debug` -This option outputs debugging information to the console. This information is useful when you're seeing a problem and having a hard time pinpointing it. The ESLint team may ask for this debugging information to help solve bugs. +This option outputs debugging information to the console. This information is useful when you're seeing a problem and having a hard time pinpointing it. The ESLint team may ask for this debugging information to help solve bugs. +Add this flag to an ESLint command line invocation in order to get extra debug information as the command is run (e.g. `eslint --debug test.js` and `eslint test.js --debug` are equivalent) #### `-h`, `--help` diff --git a/eslint/docs/user-guide/configuring/configuration-files.md b/eslint/docs/user-guide/configuring/configuration-files.md index e8aa6bf..091d043 100644 --- a/eslint/docs/user-guide/configuring/configuration-files.md +++ b/eslint/docs/user-guide/configuring/configuration-files.md @@ -6,7 +6,7 @@ * [Cascading and Hierarchy](#cascading-and-hierarchy) * [Extending Configuration Files](#extending-configuration-files) * [Configuration Based on Glob Patterns](#configuration-based-on-glob-patterns) -* [Personal Configuration Files](#personal-configuration-files) +* [Personal Configuration Files](#personal-configuration-files-deprecated) ## Configuration File Formats @@ -37,7 +37,34 @@ The second way to use configuration files is to save the file wherever you would eslint -c myconfig.json myfiletotest.js -If you are using one configuration file and want ESLint to ignore any `.eslintrc.*` files, make sure to use [`--no-eslintrc`](https://eslint.org/docs/user-guide/command-line-interface#-no-eslintrc) along with the [`-c`](https://eslint.org/docs/user-guide/command-line-interface#-c-config) flag. +If you are using one configuration file and want ESLint to ignore any `.eslintrc.*` files, make sure to use [`--no-eslintrc`](https://eslint.org/docs/user-guide/command-line-interface#--no-eslintrc) along with the [`-c`](https://eslint.org/docs/user-guide/command-line-interface#-c---config) flag. + +Here's an example JSON configuration file that uses the `typescript-eslint` parser to support TypeScript syntax: + +```json +{ + "root": true, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { "project": ["./tsconfig.json"] }, + "plugins": [ + "@typescript-eslint" + ], + "rules": { + "@typescript-eslint/strict-boolean-expressions": [ + 2, + { + "allowString" : false, + "allowNumber" : false + } + ] + }, + "ignorePatterns": ["src/**/*.test.ts", "src/frontend/generated/*"] +} +``` ### Comments in configuration files @@ -192,6 +219,10 @@ The `rules` property can do any of the following to extend (or override) the set * Base config: `"quotes": ["error", "single", "avoid-escape"]` * Derived config: `"quotes": ["error", "single"]` * Resulting actual config: `"quotes": ["error", "single"]` +* override options for rules given as object from base configurations: + * Base config: `"max-lines": ["error", { "max": 200, "skipBlankLines": true, "skipComments": true }]` + * Derived config: `"max-lines": ["error", { "max": 100 }]` + * Resulting actual config: `"max-lines": ["error", { "max": 100 }]` where `skipBlankLines` and `skipComments` default to `false` ### Using a shareable configuration package @@ -297,7 +328,7 @@ The `extends` property value can be `"eslint:all"` to enable all core rules in t You might enable all core rules as a shortcut to explore rules and options while you decide on the configuration for a project, especially if you rarely override options or disable rules. The default options for rules are not endorsements by ESLint (for example, the default option for the [`quotes`](https://eslint.org/docs/rules/quotes) rule does not mean double quotes are better than single quotes). -If your configuration extends `eslint:all`, after you upgrade to a newer major or minor version of ESLint, review the reported problems before you use the `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix), so you know if a new fixable rule will make changes to the code. +If your configuration extends `eslint:all`, after you upgrade to a newer major or minor version of ESLint, review the reported problems before you use the `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#--fix), so you know if a new fixable rule will make changes to the code. Example of a configuration file in JavaScript format: diff --git a/eslint/docs/user-guide/configuring/language-options.md b/eslint/docs/user-guide/configuring/language-options.md index eb3fe8a..1ee3f2d 100644 --- a/eslint/docs/user-guide/configuring/language-options.md +++ b/eslint/docs/user-guide/configuring/language-options.md @@ -187,7 +187,7 @@ For ES6 syntax, use `{ "parserOptions": { "ecmaVersion": 6 } }`; for new ES6 glo Parser options are set in your `.eslintrc.*` file by using the `parserOptions` property. The available options are: -* `ecmaVersion` - set to 3, 5 (default), 6, 7, 8, 9, 10, 11, or 12 to specify the version of ECMAScript syntax you want to use. You can also set to 2015 (same as 6), 2016 (same as 7), 2017 (same as 8), 2018 (same as 9), 2019 (same as 10), 2020 (same as 11), or 2021 (same as 12) to use the year-based naming. +* `ecmaVersion` - set to 3, 5 (default), 6, 7, 8, 9, 10, 11, 12, or 13 to specify the version of ECMAScript syntax you want to use. You can also set to 2015 (same as 6), 2016 (same as 7), 2017 (same as 8), 2018 (same as 9), 2019 (same as 10), 2020 (same as 11), 2021 (same as 12), or 2022 (same as 13) to use the year-based naming. You can also set "latest" to use the most recently supported version. * `sourceType` - set to `"script"` (default) or `"module"` if your code is in ECMAScript modules. * `ecmaFeatures` - an object indicating which additional language features you'd like to use: * `globalReturn` - allow `return` statements in the global scope @@ -199,7 +199,7 @@ Here's an example `.eslintrc.json` file: ```json { "parserOptions": { - "ecmaVersion": 6, + "ecmaVersion": "latest", "sourceType": "module", "ecmaFeatures": { "jsx": true diff --git a/eslint/docs/user-guide/configuring/rules.md b/eslint/docs/user-guide/configuring/rules.md index 2274b98..0191f63 100644 --- a/eslint/docs/user-guide/configuring/rules.md +++ b/eslint/docs/user-guide/configuring/rules.md @@ -20,7 +20,7 @@ To configure rules inside of a file using configuration comments, use a comment /* eslint eqeqeq: "off", curly: "error" */ ``` -In this example, [`eqeqeq`](https://eslint.org/docs/rules/eqeqeq) is turned off and [`curly`](.https://eslint.org/docs/rules/curly) is turned on as an error. You can also use the numeric equivalent for the rule severity: +In this example, [`eqeqeq`](https://eslint.org/docs/rules/eqeqeq) is turned off and [`curly`](https://eslint.org/docs/rules/curly) is turned on as an error. You can also use the numeric equivalent for the rule severity: ```js /* eslint eqeqeq: 0, curly: 2 */ diff --git a/eslint/docs/user-guide/getting-started.md b/eslint/docs/user-guide/getting-started.md index 3b28a91..3c9633b 100644 --- a/eslint/docs/user-guide/getting-started.md +++ b/eslint/docs/user-guide/getting-started.md @@ -8,7 +8,7 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J ## Installation and Usage -Prerequisites: [Node.js](https://nodejs.org/en/) (`^10.12.0`, or `>=12.0.0`) built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.) +Prerequisites: [Node.js](https://nodejs.org/en/) (`^12.22.0`, `^14.17.0`, or `>=16.0.0`) built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.) You can install ESLint using npm or yarn: diff --git a/eslint/docs/user-guide/integrations.md b/eslint/docs/user-guide/integrations.md index c7130bb..c919c2d 100644 --- a/eslint/docs/user-guide/integrations.md +++ b/eslint/docs/user-guide/integrations.md @@ -27,7 +27,7 @@ * Broccoli: [broccoli-eslint](https://www.npmjs.com/package/broccoli-eslint) * Browserify: [eslintify](https://www.npmjs.com/package/eslintify) * Webpack: [eslint-webpack-plugin](https://www.npmjs.com/package/eslint-webpack-plugin) -* Rollup: [rollup-plugin-eslint](https://www.npmjs.com/package/rollup-plugin-eslint) +* Rollup: [@rollup/plugin-eslint](https://www.npmjs.com/package/@rollup/plugin-eslint) * Ember-cli: [ember-cli-eslint](https://www.npmjs.com/package/ember-cli-eslint) * Sails.js: [sails-hook-lint](https://www.npmjs.com/package/sails-hook-lint), [sails-eslint](https://www.npmjs.com/package/sails-eslint) * Start: [@start/plugin-lib-eslint](https://www.npmjs.com/package/@start/plugin-lib-eslint) @@ -44,6 +44,7 @@ * [Git Precommit Hook](https://coderwall.com/p/zq8jlq/eslint-pre-commit-hook) * [Git pre-commit hook that only lints staged changes](https://gist.github.com/dahjelle/8ddedf0aebd488208a9a7c829f19b9e8) * [overcommit Git hook manager](https://github.com/brigade/overcommit) +* [Mega-Linter](https://nvuillam.github.io/mega-linter): Linters aggregator for CI, [embedding eslint](https://nvuillam.github.io/mega-linter/descriptors/javascript_eslint/) ## Testing diff --git a/eslint/docs/user-guide/migrating-to-8.0.0.md b/eslint/docs/user-guide/migrating-to-8.0.0.md new file mode 100644 index 0000000..351309c --- /dev/null +++ b/eslint/docs/user-guide/migrating-to-8.0.0.md @@ -0,0 +1,297 @@ +# Migrating to v8.0.0 + +ESLint v8.0.0 is a major release of ESLint. We have made a few breaking changes in this release. This guide is intended to walk you through the breaking changes. + +The lists below are ordered roughly by the number of users each change is expected to affect, where the first items are expected to affect the most users. + +## Table of Contents + +### Breaking changes for users + +- [Node.js 10, 13, and 15 are no longer supported](#drop-old-node) +- [Removed `codeframe` and `table` formatters](#removed-formatters) +- [`comma-dangle` rule schema is stricter](#comma-dangle) +- [Unused disable directives are now fixable](#directives) +- [`eslint:recommended` has been updated](#eslint-recommended) + +### Breaking changes for plugin developers + +- [Node.js 10, 13, and 15 are no longer supported](#drop-old-node) +- [Rules require `meta.hasSuggestions` to provide suggestions](#suggestions) +- [Rules require `meta.fixable` to provide fixes](#fixes) +- [`SourceCode#getComments()` fails in `RuleTester`](#get-comments) +- [Changes to shorthand property AST format](#ast-format) + +### Breaking changes for integration developers + +- [Node.js 10, 13, and 15 are no longer supported](#drop-old-node) +- [The `CLIEngine` class has been removed](#remove-cliengine) +- [The `linter` object has been removed](#remove-linter) +- [The `/lib` entrypoint has been removed](#remove-lib) + +--- + +## Node.js 10, 13, and 15 are no longer supported + +Node.js 10, 13, 15 all reached end of life either in 2020 or early 2021. ESLint is officially dropping support for these versions of Node.js starting with ESLint v8.0.0. ESLint now supports the following versions of Node.js: + +- Node.js 12.22 and above +- Node.js 14 and above +- Node.js 16 and above + +**To address:** Make sure you upgrade to at least Node.js `12.22.0` when using ESLint v8.0.0. One important thing to double check is the Node.js version supported by your editor when using ESLint via editor integrations. If you are unable to upgrade, we recommend continuing to use ESLint 7 until you are able to upgrade Node.js. + +**Related issue(s):** [#14023](https://github.com/eslint/eslint/issues/14023) + +## Removed `codeframe` and `table` formatters + +ESLint v8.0.0 has removed the `codeframe` and `table` formatters from the core. These formatters required dependencies that weren't used anywhere else in ESLint, and removing them allows us to reduce the size of ESLint, allowing for faster installation. + +**To address:** If you are using the `codeframe` or `table` formatters, you'll need to install the standalone [`eslint-formatter-codeframe`](https://github.com/fregante/eslint-formatter-codeframe) or [`eslint-formatter-table`](https://github.com/fregante/eslint-formatter-table) packages, respectively, to be able to use them in ESLint v8.0.0. + +**Related issue(s):** [#14277](https://github.com/eslint/eslint/issues/14277), [#14316](https://github.com/eslint/eslint/pull/14316) + + +## `comma-dangle` rule schema is stricter + +In ESLint v7.0.0, the `comma-dangle` rule could be configured like this without error: + +```json +{ + "rules": { + "comma-dangle": ["error", "never", { "arrays": "always" }] + } +} +``` + +With this configuration, the rule would ignore the third element in the array because only the second element is read. In ESLint v8.0.0, this configuration will cause ESLint to throw an error. + +**To address:** Change your rule configuration so that there are only two elements in the array, and the second element is either a string or an object, such as: + +```jsonc +{ + "comma-dangle": ["error", "never"], + // or + "comma-dangle": ["error", { + "arrays": "never", + "objects": "never", + "imports": "never", + "exports": "never", + "functions": "never" + }] +} +``` + +**Related issue(s):** [#13739](https://github.com/eslint/eslint/issues/13739) + +## Unused disable directives are now fixable + +In ESLint v7.0.0, using both `--report-unused-disable-directives` and `--fix` on the command line would fix only rules but leave unused disable directives in place. In ESLint v8.0.0, this combination of command-line options will result in the unused disable directives being removed. + +**To address:** If you are using `--report-unused-disable-directives` and `--fix` together on the command line, and you don't want unused disable directives to be removed, add `--fix-type problem,suggestion,layout` as a command line option. + +**Related issue(s):** [#11815](https://github.com/eslint/eslint/issues/11815) + +## `eslint:recommended` has been updated + +Four new rules have been enabled in the `eslint:recommended` preset. + +- [`no-loss-of-precision`](https://eslint.org/docs/rules/no-loss-of-precision) +- [`no-nonoctal-decimal-escape`](https://eslint.org/docs/rules/no-nonoctal-decimal-escape) +- [`no-unsafe-optional-chaining`](https://eslint.org/docs/rules/no-unsafe-optional-chaining) +- [`no-useless-backreference`](https://eslint.org/docs/rules/no-useless-backreference) + +**To address:** Fix errors or disable these rules. + +**Related issue(s):** [#14673](https://github.com/eslint/eslint/issues/14673) + + +## Rules require `meta.hasSuggestions` to provide suggestions + +In ESLint v7.0.0, rules that [provided suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions) did not need to let ESLint know. In v8.0.0, rules providing suggestions need to set their `meta.hasSuggestions` to `true`. This informs ESLint that the rule intends to provide suggestions. Without this property, any attempt to provide a suggestion will result in an error. + +**To address:** If your rule provides suggestions, add `meta.hasSuggestions` to the object, such as: + +```js +module.exports = { + meta: { + hasSuggestions: true + }, + create(context) { + // your rule + } +}; +``` + +The [eslint-plugin/require-meta-has-suggestions](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/require-meta-has-suggestions.md) rule can automatically fix and enforce that your rules are properly specifying `meta.hasSuggestions`. + +**Related issue(s):** [#14312](https://github.com/eslint/eslint/issues/14312) + +## Rules require `meta.fixable` to provide fixes + +In ESLint v7.0.0, rules that were written as a function (rather than object) were able to provide fixes. In ESLint v8.0.0, only rules written as an object are allowed to provide fixes and must have a `meta.fixable` property set to either `"code"` or `"whitespace"`. + +**To address:** If your rule makes fixes and is written as a function, such as: + +```js +module.exports = function(context) { + // your rule +}; +``` + +Then rewrite your rule in this format: + +```js +module.exports = { + meta: { + fixable: "code" // or "whitespace" + }, + create(context) { + // your rule + } +}; +``` + +The [eslint-plugin/require-meta-fixable](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/require-meta-fixable.md) rule can automatically fix and enforce that your rules are properly specifying `meta.fixable`. + +The [eslint-plugin/prefer-object-rule](https://github.com/not-an-aardvark/eslint-plugin-eslint-plugin/blob/master/docs/rules/prefer-object-rule.md) rule can automatically fix and enforce that your rules are written with the object format instead of the deprecated function format. + +See the [rule documentation](https://eslint.org/docs/developer-guide/working-with-rules) for more information on writing rules. + +**Related issue(s):** [#13349](https://github.com/eslint/eslint/issues/13349) + +## `SourceCode#getComments()` fails in `RuleTester` + +Back in ESLint v4.0.0, we deprecated `SourceCode#getComments()`, but we neglected to remove it. Rather than removing it completely in v8.0.0, we are taking the intermediate step of updating `RuleTester` to fail when `SourceCode#getComments()` is used inside of a rule. As such, all existing rules will continue to work, but when the developer runs tests for the rule there will be a failure. + +The `SourceCode#getComments()` method will be removed in v9.0.0. + +**To address:** If your rule uses `SourceCode#getComments()`, please use [`SourceCode#getCommentsBefore()`, `SourceCode#getCommentsAfter()`, or `SourceCode#getCommentsInside()`](https://eslint.org/docs/developer-guide/working-with-rules#sourcecodegetcommentsbefore-sourcecodegetcommentsafter-and-sourcecodegetcommentsinside). + +**Related issue(s):** [#14744](https://github.com/eslint/eslint/issues/14744) + +## Changes to shorthand property AST format + +ESLint v8.0.0 includes an upgrade to Espree v8.0.0 to support new syntax. This Espree upgrade, in turn, contains an upgrade to Acorn v8.0.0, which changed how shorthand properties were represented in the AST. Here's an example: + +```js +const version = 8; +const x = { + version +}; +``` + +This code creates a property node that looks like this: + +```json +{ + "type": "Property", + "method": false, + "shorthand": true, + "computed": false, + "key": { + "type": "Identifier", + "name": "version" + }, + "kind": "init", + "value": { + "type": "Identifier", + "name": "version" + } +} +``` + +Note that both the `key` and the `value` properties contain the same information. Prior to Acorn v8.0.0 (and therefore prior to ESLint v8.0.0), these two nodes were represented by the same object, so you could use `===` to determine if they represented the same node, such as: + +```js +// true in ESLint v7.x, false in ESLint v8.0.0 +if (propertyNode.key === propertyNode.value) { + // do something +} +``` + +In ESLint v8.0.0 (via Acorn v8.0.0), the key and value are now separate objects and therefore no longer equivalent. + +**To address:** If your rule makes a comparison between the key and value of a shorthand object literal property to determine if they are the same node, you'll need to change your code in one of two ways: + +1. Use `propertyNode.shorthand` to determine if the property is a shorthand property node. +1. Use the `range` property of each node to determine if the key and value occupy the same location. + +**Related issue(s):** [#14591](https://github.com/eslint/eslint/pull/14591#issuecomment-887733070) + + +## The `CLIEngine` class has been removed + +The `CLIEngine` class has been removed and replaced by the [`ESLint` class](https://eslint.org/docs/developer-guide/nodejs-api#eslint-class). + +**To address:** Update your code to use the new `ESLint` class if you are currently using `CLIEngine`. The following table maps the existing `CLIEngine` methods to their `ESLint` counterparts: + +| `CLIEngine` | `ESLint` | +| :------------------------------------------- | :--------------------------------- | +| `executeOnFiles(patterns)` | `lintFiles(patterns)` | +| `executeOnText(text, filePath, warnIgnored)` | `lintText(text, options)` | +| `getFormatter(name)` | `loadFormatter(name)` | +| `getConfigForFile(filePath)` | `calculateConfigForFile(filePath)` | +| `isPathIgnored(filePath)` | `isPathIgnored(filePath)` | +| `static outputFixes(results)` | `static outputFixes(results)` | +| `static getErrorResults(results)` | `static getErrorResults(results)` | +| `static getFormatter(name)` | (removed ※1) | +| `addPlugin(pluginId, definition)` | the `plugins` constructor option | +| `getRules()` | (removed ※2) | +| `resolveFileGlobPatterns()` | (removed ※3) | + +- ※1 The `engine.getFormatter()` method currently returns the object of loaded packages as-is, which made it difficult to add new features to formatters for backward compatibility reasons. The new `eslint.loadFormatter()` method returns an adapter object that wraps the object of loaded packages, to ease the process of adding new features. Additionally, the adapter object has access to the `ESLint` instance to calculate default data (using loaded plugin rules to make `rulesMeta`, for example). As a result, the `ESLint` class only implements an instance version of the `loadFormatter()` method. +- ※2 The `CLIEngine#getRules()` method had side effects and so was removed. If you were using `CLIEngine#getRules()` to retrieve meta information about rules based on linting results, use `ESLint#getRulesMetaForResults()` instead. If you were using `CLIEngine#getRules()` to retrieve all built-in rules, import `builtinRules` from `eslint/use-at-your-own-risk` for an unsupported API that allows access to internal rules. +- ※3 Since ESLint v6.0.0, ESLint uses different logic from the `resolveFileGlobPatterns()` method to iterate files, making this method obsolete. + +**Related issue(s):** [RFC80](https://github.com/eslint/rfcs/tree/main/designs/2021-package-exports), [#14716](https://github.com/eslint/eslint/pull/14716), [#13654](https://github.com/eslint/eslint/issues/13654) + +## The `linter` object has been removed + +The deprecated `linter` object has been removed from the ESLint package in v8.0.0. + +**To address:** If you are using the `linter` object, such as: + +```js +const { linter } = require("eslint"); +``` + +Change your code to this: + +```js +const { Linter } = require("eslint"); +const linter = new Linter(); +``` + +**Related issue(s):** [RFC80](https://github.com/eslint/rfcs/tree/main/designs/2021-package-exports), [#14716](https://github.com/eslint/eslint/pull/14716), [#13654](https://github.com/eslint/eslint/issues/13654) + +## The `/lib` entrypoint has been removed + +Beginning in v8.0.0, ESLint is strictly defining its public API. Previously, you could reach into individual files such as `require("eslint/lib/rules/semi")` and this is no longer allowed. There are a limited number of existing APIs that are now available through the `/use-at-your-own-risk` entrypoint for backwards compatibility, but these APIs are not formally supported and may break or disappear at any point in time. + +**To address:** If you are accessing rules directly through the `/lib` entrypoint, such as: + +```js +const rule = require("eslint/lib/rules/semi"); +``` + +Change your code to this: + +```js +const { builtinRules } = require("eslint/use-at-your-own-risk"); +const rule = builtinRules.get("semi"); +``` + +If you are accessing `FileEnumerator` directly through the `/lib` entrypoint, such as: + +```js +const { FileEnumerator } = require("eslint/lib/cli-engine/file-enumerator"); +``` + +Change your code to this: + +```js +const { FileEnumerator } = require("eslint/use-at-your-own-risk"); +``` + +**Related issue(s):** [RFC80](https://github.com/eslint/rfcs/tree/main/designs/2021-package-exports), [#14716](https://github.com/eslint/eslint/pull/14716), [#13654](https://github.com/eslint/eslint/issues/13654) diff --git a/eslint/lib/api.js b/eslint/lib/api.js index e4b6643..3dde098 100644 --- a/eslint/lib/api.js +++ b/eslint/lib/api.js @@ -5,30 +5,22 @@ "use strict"; -const { CLIEngine } = require("./cli-engine"); +//----------------------------------------------------------------------------- +// Requirements +//----------------------------------------------------------------------------- + const { ESLint } = require("./eslint"); const { Linter } = require("./linter"); const { RuleTester } = require("./rule-tester"); const { SourceCode } = require("./source-code"); +//----------------------------------------------------------------------------- +// Exports +//----------------------------------------------------------------------------- + module.exports = { Linter, - CLIEngine, ESLint, RuleTester, SourceCode }; - -// DOTO: remove deprecated API. -let deprecatedLinterInstance = null; - -Object.defineProperty(module.exports, "linter", { - enumerable: false, - get() { - if (!deprecatedLinterInstance) { - deprecatedLinterInstance = new Linter(); - } - - return deprecatedLinterInstance; - } -}); diff --git a/eslint/lib/cli-engine/cli-engine.js b/eslint/lib/cli-engine/cli-engine.js index ca298f9..e364701 100644 --- a/eslint/lib/cli-engine/cli-engine.js +++ b/eslint/lib/cli-engine/cli-engine.js @@ -41,7 +41,7 @@ const hash = require("./hash"); const LintResultCache = require("./lint-result-cache"); const debug = require("debug")("eslint:cli-engine"); -const validFixTypes = new Set(["problem", "suggestion", "layout"]); +const validFixTypes = new Set(["directive", "problem", "suggestion", "layout"]); //------------------------------------------------------------------------------ // Typedefs @@ -55,8 +55,8 @@ const validFixTypes = new Set(["problem", "suggestion", "layout"]); /** @typedef {import("../shared/types").Plugin} Plugin */ /** @typedef {import("../shared/types").RuleConf} RuleConf */ /** @typedef {import("../shared/types").Rule} Rule */ -/** @typedef {ReturnType} ConfigArray */ -/** @typedef {ReturnType} ExtractedConfig */ +/** @typedef {ReturnType} ConfigArray */ +/** @typedef {ReturnType} ExtractedConfig */ /** * The options to configure a CLI engine with. @@ -156,6 +156,9 @@ function calculateStatsPerFile(messages) { return messages.reduce((stat, message) => { if (message.fatal || message.severity === 2) { stat.errorCount++; + if (message.fatal) { + stat.fatalErrorCount++; + } if (message.fix) { stat.fixableErrorCount++; } @@ -168,6 +171,7 @@ function calculateStatsPerFile(messages) { return stat; }, { errorCount: 0, + fatalErrorCount: 0, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0 @@ -183,12 +187,14 @@ function calculateStatsPerFile(messages) { function calculateStatsPerRun(results) { return results.reduce((stat, result) => { stat.errorCount += result.errorCount; + stat.fatalErrorCount += result.fatalErrorCount; stat.warningCount += result.warningCount; stat.fixableErrorCount += result.fixableErrorCount; stat.fixableWarningCount += result.fixableWarningCount; return stat; }, { errorCount: 0, + fatalErrorCount: 0, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0 @@ -274,7 +280,7 @@ function verifyText({ /** * Returns result with warning by ignore settings * @param {string} filePath File path of checked code - * @param {string} baseDir Absolute path of base directory + * @param {string} baseDir Absolute path of base directory * @returns {LintResult} Result with single warning * @private */ @@ -325,6 +331,23 @@ function getRule(ruleId, configArrays) { return builtInRules.get(ruleId) || null; } +/** + * Checks whether a message's rule type should be fixed. + * @param {LintMessage} message The message to check. + * @param {ConfigArray[]} lastConfigArrays The list of config arrays that the last `executeOnFiles` or `executeOnText` used. + * @param {string[]} fixTypes An array of fix types to check. + * @returns {boolean} Whether the message should be fixed. + */ +function shouldMessageBeFixed(message, lastConfigArrays, fixTypes) { + if (!message.ruleId) { + return fixTypes.has("directive"); + } + + const rule = message.ruleId && getRule(message.ruleId, lastConfigArrays); + + return Boolean(rule && rule.meta && fixTypes.has(rule.meta.type)); +} + /** * Collect used deprecated rules. * @param {ConfigArray[]} usedConfigArrays The config arrays which were used. @@ -457,6 +480,7 @@ function getCacheFile(cacheFile, cwd) { * @param {string[]|null} keys The keys to assign true. * @param {boolean} defaultValue The default value for each property. * @param {string} displayName The property name which is used in error message. + * @throws {Error} Requires array. * @returns {Record} The boolean map. */ function toBooleanMap(keys, defaultValue, displayName) { @@ -520,6 +544,7 @@ function createConfigDataFromOptions(options) { /** * Checks whether a directory exists at the given location * @param {string} resolvedPath A path from the CWD + * @throws {Error} As thrown by `fs.statSync` or `fs.isDirectory`. * @returns {boolean} `true` if a directory exists */ function directoryExists(resolvedPath) { @@ -537,13 +562,18 @@ function directoryExists(resolvedPath) { // Public Interface //------------------------------------------------------------------------------ +/** + * Core CLI. + */ class CLIEngine { /** * Creates a new instance of the core CLI engine. * @param {CLIEngineOptions} providedOptions The options for this instance. + * @param {Object} [additionalData] Additional settings that are not CLIEngineOptions. + * @param {Record|null} [additionalData.preloadedPlugins] Preloaded plugins. */ - constructor(providedOptions) { + constructor(providedOptions, { preloadedPlugins } = {}) { const options = Object.assign( Object.create(null), defaultOptions, @@ -556,6 +586,13 @@ class CLIEngine { } const additionalPluginPool = new Map(); + + if (preloadedPlugins) { + for (const [id, plugin] of Object.entries(preloadedPlugins)) { + additionalPluginPool.set(id, plugin); + } + } + const cacheFilePath = getCacheFile( options.cacheLocation || options.cacheFile, options.cwd @@ -617,12 +654,7 @@ class CLIEngine { const originalFix = (typeof options.fix === "function") ? options.fix : () => true; - options.fix = message => { - const rule = message.ruleId && getRule(message.ruleId, lastConfigArrays); - const matches = rule && rule.meta && fixTypes.has(rule.meta.type); - - return matches && originalFix(message); - }; + options.fix = message => shouldMessageBeFixed(message, lastConfigArrays, fixTypes) && originalFix(message); } } @@ -675,26 +707,6 @@ class CLIEngine { }); } - - /** - * Add a plugin by passing its configuration - * @param {string} name Name of the plugin. - * @param {Plugin} pluginObject Plugin configuration object. - * @returns {void} - */ - addPlugin(name, pluginObject) { - const { - additionalPluginPool, - configArrayFactory, - lastConfigArrays - } = internalSlotsMap.get(this); - - additionalPluginPool.set(name, pluginObject); - configArrayFactory.clearCache(); - lastConfigArrays.length = 1; - lastConfigArrays[0] = configArrayFactory.getConfigArrayForFile(); - } - /** * Resolves the patterns passed into executeOnFiles() into glob-based patterns * for easier handling. @@ -724,6 +736,7 @@ class CLIEngine { /** * Executes the current configuration on an array of file and directory names. * @param {string[]} patterns An array of file and directory names. + * @throws {Error} As may be thrown by `fs.unlinkSync`. * @returns {LintReport} The results for all files that were linted. */ executeOnFiles(patterns) { @@ -930,6 +943,7 @@ class CLIEngine { * This is the same logic used by the ESLint CLI executable to determine * configuration for each file it processes. * @param {string} filePath The path of the file to retrieve a config object for. + * @throws {Error} If filepath a directory path. * @returns {ConfigData} A configuration object for the file. */ getConfigForFile(filePath) { @@ -978,6 +992,7 @@ class CLIEngine { * Returns the formatter representing the given format or null if the `format` is not a string. * @param {string} [format] The name of the format to load or the path to a * custom formatter. + * @throws {any} As may be thrown by requiring of formatter * @returns {(Function|null)} The formatter function or null if the `format` is not a string. */ getFormatter(format) { @@ -1013,7 +1028,11 @@ class CLIEngine { try { return require(formatterPath); } catch (ex) { - ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`; + if (format === "table" || format === "codeframe") { + ex.message = `The ${format} formatter is no longer part of core ESLint. Install it manually with \`npm install -D eslint-formatter-${format}\``; + } else { + ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`; + } throw ex; } diff --git a/eslint/lib/cli-engine/file-enumerator.js b/eslint/lib/cli-engine/file-enumerator.js index ade2851..f1442d1 100644 --- a/eslint/lib/cli-engine/file-enumerator.js +++ b/eslint/lib/cli-engine/file-enumerator.js @@ -60,7 +60,7 @@ const IGNORED_SILENTLY = 1; const IGNORED = 2; // For VSCode intellisense -/** @typedef {ReturnType} ConfigArray */ +/** @typedef {ReturnType} ConfigArray */ /** * @typedef {Object} FileEnumeratorOptions @@ -114,6 +114,7 @@ function isGlobPattern(pattern) { /** * Get stats of a given path. * @param {string} filePath The path to target file. + * @throws {Error} As may be thrown by `fs.statSync`. * @returns {fs.Stats|null} The stats. * @private */ @@ -132,6 +133,7 @@ function statSafeSync(filePath) { /** * Get filenames in a given path to a directory. * @param {string} directoryPath The path to target directory. + * @throws {Error} As may be thrown by `fs.readdirSync`. * @returns {import("fs").Dirent[]} The filenames. * @private */ @@ -173,7 +175,6 @@ function createExtensionRegExp(extensions) { */ class NoFilesFoundError extends Error { - // eslint-disable-next-line jsdoc/require-description /** * @param {string} pattern The glob pattern which was not found. * @param {boolean} globDisabled If `true` then the pattern was a glob pattern, but glob was disabled. @@ -190,7 +191,6 @@ class NoFilesFoundError extends Error { */ class AllFilesIgnoredError extends Error { - // eslint-disable-next-line jsdoc/require-description /** * @param {string} pattern The glob pattern which was not found. */ @@ -270,6 +270,7 @@ class FileEnumerator { /** * Iterate files which are matched by given glob patterns. * @param {string|string[]} patternOrPatterns The glob patterns to iterate files. + * @throws {NoFilesFoundError|AllFilesIgnoredError} On an unmatched pattern. * @returns {IterableIterator} The found files. */ *iterateFiles(patternOrPatterns) { diff --git a/eslint/lib/cli-engine/formatters/codeframe.js b/eslint/lib/cli-engine/formatters/codeframe.js deleted file mode 100644 index 41e3ab7..0000000 --- a/eslint/lib/cli-engine/formatters/codeframe.js +++ /dev/null @@ -1,138 +0,0 @@ -/** - * @fileoverview Codeframe reporter - * @author Vitor Balocco - */ -"use strict"; - -const chalk = require("chalk"); -const { codeFrameColumns } = require("@babel/code-frame"); -const path = require("path"); - -//------------------------------------------------------------------------------ -// Helpers -//------------------------------------------------------------------------------ - -/** - * Given a word and a count, append an s if count is not one. - * @param {string} word A word in its singular form. - * @param {number} count A number controlling whether word should be pluralized. - * @returns {string} The original word with an s on the end if count is not one. - */ -function pluralize(word, count) { - return (count === 1 ? word : `${word}s`); -} - -/** - * Gets a formatted relative file path from an absolute path and a line/column in the file. - * @param {string} filePath The absolute file path to format. - * @param {number} line The line from the file to use for formatting. - * @param {number} column The column from the file to use for formatting. - * @returns {string} The formatted file path. - */ -function formatFilePath(filePath, line, column) { - let relPath = path.relative(process.cwd(), filePath); - - if (line && column) { - relPath += `:${line}:${column}`; - } - - return chalk.green(relPath); -} - -/** - * Gets the formatted output for a given message. - * @param {Object} message The object that represents this message. - * @param {Object} parentResult The result object that this message belongs to. - * @returns {string} The formatted output. - */ -function formatMessage(message, parentResult) { - const type = (message.fatal || message.severity === 2) ? chalk.red("error") : chalk.yellow("warning"); - const msg = `${chalk.bold(message.message.replace(/([^ ])\.$/u, "$1"))}`; - const ruleId = message.fatal ? "" : chalk.dim(`(${message.ruleId})`); - const filePath = formatFilePath(parentResult.filePath, message.line, message.column); - const sourceCode = parentResult.output ? parentResult.output : parentResult.source; - - const firstLine = [ - `${type}:`, - `${msg}`, - ruleId ? `${ruleId}` : "", - sourceCode ? `at ${filePath}:` : `at ${filePath}` - ].filter(String).join(" "); - - const result = [firstLine]; - - if (sourceCode) { - result.push( - codeFrameColumns(sourceCode, { start: { line: message.line, column: message.column } }, { highlightCode: false }) - ); - } - - return result.join("\n"); -} - -/** - * Gets the formatted output summary for a given number of errors and warnings. - * @param {number} errors The number of errors. - * @param {number} warnings The number of warnings. - * @param {number} fixableErrors The number of fixable errors. - * @param {number} fixableWarnings The number of fixable warnings. - * @returns {string} The formatted output summary. - */ -function formatSummary(errors, warnings, fixableErrors, fixableWarnings) { - const summaryColor = errors > 0 ? "red" : "yellow"; - const summary = []; - const fixablesSummary = []; - - if (errors > 0) { - summary.push(`${errors} ${pluralize("error", errors)}`); - } - - if (warnings > 0) { - summary.push(`${warnings} ${pluralize("warning", warnings)}`); - } - - if (fixableErrors > 0) { - fixablesSummary.push(`${fixableErrors} ${pluralize("error", fixableErrors)}`); - } - - if (fixableWarnings > 0) { - fixablesSummary.push(`${fixableWarnings} ${pluralize("warning", fixableWarnings)}`); - } - - let output = chalk[summaryColor].bold(`${summary.join(" and ")} found.`); - - if (fixableErrors || fixableWarnings) { - output += chalk[summaryColor].bold(`\n${fixablesSummary.join(" and ")} potentially fixable with the \`--fix\` option.`); - } - - return output; -} - -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ - -module.exports = function(results) { - let errors = 0; - let warnings = 0; - let fixableErrors = 0; - let fixableWarnings = 0; - - const resultsWithMessages = results.filter(result => result.messages.length > 0); - - let output = resultsWithMessages.reduce((resultsOutput, result) => { - const messages = result.messages.map(message => `${formatMessage(message, result)}\n\n`); - - errors += result.errorCount; - warnings += result.warningCount; - fixableErrors += result.fixableErrorCount; - fixableWarnings += result.fixableWarningCount; - - return resultsOutput.concat(messages); - }, []).join("\n"); - - output += "\n"; - output += formatSummary(errors, warnings, fixableErrors, fixableWarnings); - - return (errors + warnings) > 0 ? output : ""; -}; diff --git a/eslint/lib/cli-engine/formatters/html.js b/eslint/lib/cli-engine/formatters/html.js index baddb63..e28996f 100644 --- a/eslint/lib/cli-engine/formatters/html.js +++ b/eslint/lib/cli-engine/formatters/html.js @@ -281,8 +281,8 @@ function resultTemplate(it) { `.trimLeft(); } -// eslint-disable-next-line jsdoc/require-description /** + * Render the results. * @param {Array} results Test results. * @param {Object} rulesMeta Dictionary containing metadata for each rule executed by the analysis. * @returns {string} HTML string describing the results. diff --git a/eslint/lib/cli-engine/formatters/table.js b/eslint/lib/cli-engine/formatters/table.js deleted file mode 100644 index a74cce0..0000000 --- a/eslint/lib/cli-engine/formatters/table.js +++ /dev/null @@ -1,159 +0,0 @@ -/** - * @fileoverview "table reporter. - * @author Gajus Kuizinas - */ -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const chalk = require("chalk"), - table = require("table").table; - -//------------------------------------------------------------------------------ -// Helpers -//------------------------------------------------------------------------------ - -/** - * Given a word and a count, append an "s" if count is not one. - * @param {string} word A word. - * @param {number} count Quantity. - * @returns {string} The original word with an s on the end if count is not one. - */ -function pluralize(word, count) { - return (count === 1 ? word : `${word}s`); -} - -/** - * Draws text table. - * @param {Array} messages Error messages relating to a specific file. - * @returns {string} A text table. - */ -function drawTable(messages) { - const rows = []; - - if (messages.length === 0) { - return ""; - } - - rows.push([ - chalk.bold("Line"), - chalk.bold("Column"), - chalk.bold("Type"), - chalk.bold("Message"), - chalk.bold("Rule ID") - ]); - - messages.forEach(message => { - let messageType; - - if (message.fatal || message.severity === 2) { - messageType = chalk.red("error"); - } else { - messageType = chalk.yellow("warning"); - } - - rows.push([ - message.line || 0, - message.column || 0, - messageType, - message.message, - message.ruleId || "" - ]); - }); - - return table(rows, { - columns: { - 0: { - width: 8, - wrapWord: true - }, - 1: { - width: 8, - wrapWord: true - }, - 2: { - width: 8, - wrapWord: true - }, - 3: { - paddingRight: 5, - width: 50, - wrapWord: true - }, - 4: { - width: 20, - wrapWord: true - } - }, - drawHorizontalLine(index) { - return index === 1; - } - }); -} - -/** - * Draws a report (multiple tables). - * @param {Array} results Report results for every file. - * @returns {string} A column of text tables. - */ -function drawReport(results) { - let files; - - files = results.map(result => { - if (!result.messages.length) { - return ""; - } - - return `\n${result.filePath}\n\n${drawTable(result.messages)}`; - }); - - files = files.filter(content => content.trim()); - - return files.join(""); -} - -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ - -module.exports = function(report) { - let result, - errorCount, - warningCount; - - result = ""; - errorCount = 0; - warningCount = 0; - - report.forEach(fileReport => { - errorCount += fileReport.errorCount; - warningCount += fileReport.warningCount; - }); - - if (errorCount || warningCount) { - result = drawReport(report); - } - - result += `\n${table([ - [ - chalk.red(pluralize(`${errorCount} Error`, errorCount)) - ], - [ - chalk.yellow(pluralize(`${warningCount} Warning`, warningCount)) - ] - ], { - columns: { - 0: { - width: 110, - wrapWord: true - } - }, - drawHorizontalLine() { - return true; - } - })}`; - - return result; -}; diff --git a/eslint/lib/cli-engine/formatters/tap.js b/eslint/lib/cli-engine/formatters/tap.js index 354872a..e4148a3 100644 --- a/eslint/lib/cli-engine/formatters/tap.js +++ b/eslint/lib/cli-engine/formatters/tap.js @@ -31,7 +31,7 @@ function outputDiagnostics(diagnostic) { const prefix = " "; let output = `${prefix}---\n`; - output += prefix + yaml.safeDump(diagnostic).split("\n").join(`\n${prefix}`); + output += prefix + yaml.dump(diagnostic).split("\n").join(`\n${prefix}`); output += "...\n"; return output; } diff --git a/eslint/lib/cli-engine/hash.js b/eslint/lib/cli-engine/hash.js index 6d7ef8b..8e46773 100644 --- a/eslint/lib/cli-engine/hash.js +++ b/eslint/lib/cli-engine/hash.js @@ -21,8 +21,8 @@ const murmur = require("imurmurhash"); /** * hash the given string - * @param {string} str the string to hash - * @returns {string} the hash + * @param {string} str the string to hash + * @returns {string} the hash */ function hash(str) { return murmur(str).result().toString(36); diff --git a/eslint/lib/cli-engine/xml-escape.js b/eslint/lib/cli-engine/xml-escape.js index 175c2c0..2e52dba 100644 --- a/eslint/lib/cli-engine/xml-escape.js +++ b/eslint/lib/cli-engine/xml-escape.js @@ -15,7 +15,7 @@ * @private */ module.exports = function(s) { - return (`${s}`).replace(/[<>&"'\x00-\x1F\x7F\u0080-\uFFFF]/gu, c => { // eslint-disable-line no-control-regex + return (`${s}`).replace(/[<>&"'\x00-\x1F\x7F\u0080-\uFFFF]/gu, c => { // eslint-disable-line no-control-regex -- Converting controls to entities switch (c) { case "<": return "<"; diff --git a/eslint/lib/cli.js b/eslint/lib/cli.js index f766764..477310d 100644 --- a/eslint/lib/cli.js +++ b/eslint/lib/cli.js @@ -131,14 +131,16 @@ function translateOptions({ */ function countErrors(results) { let errorCount = 0; + let fatalErrorCount = 0; let warningCount = 0; for (const result of results) { errorCount += result.errorCount; + fatalErrorCount += result.fatalErrorCount; warningCount += result.warningCount; } - return { errorCount, warningCount }; + return { errorCount, fatalErrorCount, warningCount }; } /** @@ -314,9 +316,12 @@ const cli = { if (await printResults(engine, resultsToPrint, options.format, options.outputFile)) { // Errors and warnings from the original unfiltered results should determine the exit code - const { errorCount, warningCount } = countErrors(results); + const { errorCount, fatalErrorCount, warningCount } = countErrors(results); + const tooManyWarnings = options.maxWarnings >= 0 && warningCount > options.maxWarnings; + const shouldExitForFatalErrors = + options.exitOnFatalError && fatalErrorCount > 0; if (!errorCount && tooManyWarnings) { log.error( @@ -325,6 +330,10 @@ const cli = { ); } + if (shouldExitForFatalErrors) { + return 2; + } + return (errorCount || tooManyWarnings) ? 1 : 0; } diff --git a/eslint/lib/config/default-config.js b/eslint/lib/config/default-config.js new file mode 100644 index 0000000..cb6f403 --- /dev/null +++ b/eslint/lib/config/default-config.js @@ -0,0 +1,52 @@ +/** + * @fileoverview Default configuration + * @author Nicholas C. Zakas + */ + +"use strict"; + +//----------------------------------------------------------------------------- +// Requirements +//----------------------------------------------------------------------------- + +const Rules = require("../rules"); + +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + + +exports.defaultConfig = [ + { + plugins: { + "@": { + parsers: { + espree: require("espree") + }, + + /* + * Because we try to delay loading rules until absolutely + * necessary, a proxy allows us to hook into the lazy-loading + * aspect of the rules map while still keeping all of the + * relevant configuration inside of the config array. + */ + rules: new Proxy({}, { + get(target, property) { + return Rules.get(property); + }, + + has(target, property) { + return Rules.has(property); + } + }) + } + }, + ignores: [ + "**/node_modules/**", + ".git/**" + ], + languageOptions: { + parser: "@/espree" + } + } +]; diff --git a/eslint/lib/config/flat-config-array.js b/eslint/lib/config/flat-config-array.js new file mode 100644 index 0000000..ef9cb33 --- /dev/null +++ b/eslint/lib/config/flat-config-array.js @@ -0,0 +1,125 @@ +/** + * @fileoverview Flat Config Array + * @author Nicholas C. Zakas + */ + +"use strict"; + +//----------------------------------------------------------------------------- +// Requirements +//----------------------------------------------------------------------------- + +const { ConfigArray, ConfigArraySymbol } = require("@humanwhocodes/config-array"); +const { flatConfigSchema } = require("./flat-config-schema"); +const { RuleValidator } = require("./rule-validator"); +const { defaultConfig } = require("./default-config"); +const recommendedConfig = require("../../conf/eslint-recommended"); +const allConfig = require("../../conf/eslint-all"); + +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + +const ruleValidator = new RuleValidator(); + +/** + * Splits a plugin identifier in the form a/b/c into two parts: a/b and c. + * @param {string} identifier The identifier to parse. + * @returns {{objectName: string, pluginName: string}} The parts of the plugin + * name. + */ +function splitPluginIdentifier(identifier) { + const parts = identifier.split("/"); + + return { + objectName: parts.pop(), + pluginName: parts.join("/") + }; +} + +//----------------------------------------------------------------------------- +// Exports +//----------------------------------------------------------------------------- + +/** + * Represents an array containing configuration information for ESLint. + */ +class FlatConfigArray extends ConfigArray { + + /** + * Creates a new instance. + * @param {*[]} configs An array of configuration information. + * @param {{basePath: string, baseConfig: FlatConfig}} options The options + * to use for the config array instance. + */ + constructor(configs, { basePath, baseConfig = defaultConfig }) { + super(configs, { + basePath, + schema: flatConfigSchema + }); + + this.unshift(baseConfig); + } + + /* eslint-disable class-methods-use-this -- Desired as instance method */ + /** + * Replaces a config with another config to allow us to put strings + * in the config array that will be replaced by objects before + * normalization. + * @param {Object} config The config to preprocess. + * @returns {Object} The preprocessed config. + */ + [ConfigArraySymbol.preprocessConfig](config) { + if (config === "eslint:recommended") { + return recommendedConfig; + } + + if (config === "eslint:all") { + return allConfig; + } + + return config; + } + + /** + * Finalizes the config by replacing plugin references with their objects + * and validating rule option schemas. + * @param {Object} config The config to finalize. + * @returns {Object} The finalized config. + * @throws {TypeError} If the config is invalid. + */ + [ConfigArraySymbol.finalizeConfig](config) { + + const { plugins, languageOptions, processor } = config; + + // Check parser value + if (languageOptions && languageOptions.parser && typeof languageOptions.parser === "string") { + const { pluginName, objectName: parserName } = splitPluginIdentifier(languageOptions.parser); + + if (!plugins || !plugins[pluginName] || !plugins[pluginName].parsers || !plugins[pluginName].parsers[parserName]) { + throw new TypeError(`Key "parser": Could not find "${parserName}" in plugin "${pluginName}".`); + } + + languageOptions.parser = plugins[pluginName].parsers[parserName]; + } + + // Check processor value + if (processor && typeof processor === "string") { + const { pluginName, objectName: processorName } = splitPluginIdentifier(processor); + + if (!plugins || !plugins[pluginName] || !plugins[pluginName].processors || !plugins[pluginName].processors[processorName]) { + throw new TypeError(`Key "processor": Could not find "${processorName}" in plugin "${pluginName}".`); + } + + config.processor = plugins[pluginName].processors[processorName]; + } + + ruleValidator.validate(config); + + return config; + } + /* eslint-enable class-methods-use-this -- Desired as instance method */ + +} + +exports.FlatConfigArray = FlatConfigArray; diff --git a/eslint/lib/config/flat-config-schema.js b/eslint/lib/config/flat-config-schema.js new file mode 100644 index 0000000..c8cc711 --- /dev/null +++ b/eslint/lib/config/flat-config-schema.js @@ -0,0 +1,452 @@ +/** + * @fileoverview Flat config schema + * @author Nicholas C. Zakas + */ + +"use strict"; + +//----------------------------------------------------------------------------- +// Type Definitions +//----------------------------------------------------------------------------- + +/** + * @typedef ObjectPropertySchema + * @property {Function|string} merge The function or name of the function to call + * to merge multiple objects with this property. + * @property {Function|string} validate The function or name of the function to call + * to validate the value of this property. + */ + +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + +const ruleSeverities = new Map([ + [0, 0], ["off", 0], + [1, 1], ["warn", 1], + [2, 2], ["error", 2] +]); + +const globalVariablesValues = new Set([ + true, "true", "writable", "writeable", + false, "false", "readonly", "readable", null, + "off" +]); + +/** + * Check if a value is a non-null object. + * @param {any} value The value to check. + * @returns {boolean} `true` if the value is a non-null object. + */ +function isNonNullObject(value) { + return typeof value === "object" && value !== null; +} + +/** + * Check if a value is undefined. + * @param {any} value The value to check. + * @returns {boolean} `true` if the value is undefined. + */ +function isUndefined(value) { + return typeof value === "undefined"; +} + +/** + * Deeply merges two objects. + * @param {Object} first The base object. + * @param {Object} second The overrides object. + * @returns {Object} An object with properties from both first and second. + */ +function deepMerge(first = {}, second = {}) { + + /* + * If the second value is an array, just return it. We don't merge + * arrays because order matters and we can't know the correct order. + */ + if (Array.isArray(second)) { + return second; + } + + /* + * First create a result object where properties from the second object + * overwrite properties from the first. This sets up a baseline to use + * later rather than needing to inspect and change every property + * individually. + */ + const result = { + ...first, + ...second + }; + + for (const key of Object.keys(second)) { + + // avoid hairy edge case + if (key === "__proto__") { + continue; + } + + const firstValue = first[key]; + const secondValue = second[key]; + + if (isNonNullObject(firstValue)) { + result[key] = deepMerge(firstValue, secondValue); + } else if (isUndefined(firstValue)) { + if (isNonNullObject(secondValue)) { + result[key] = deepMerge( + Array.isArray(secondValue) ? [] : {}, + secondValue + ); + } else if (!isUndefined(secondValue)) { + result[key] = secondValue; + } + } + } + + return result; + +} + +/** + * Normalizes the rule options config for a given rule by ensuring that + * it is an array and that the first item is 0, 1, or 2. + * @param {Array|string|number} ruleOptions The rule options config. + * @returns {Array} An array of rule options. + */ +function normalizeRuleOptions(ruleOptions) { + + const finalOptions = Array.isArray(ruleOptions) + ? ruleOptions.slice(0) + : [ruleOptions]; + + finalOptions[0] = ruleSeverities.get(finalOptions[0]); + return finalOptions; +} + +//----------------------------------------------------------------------------- +// Assertions +//----------------------------------------------------------------------------- + +/** + * Validates that a value is a valid rule options entry. + * @param {any} value The value to check. + * @returns {void} + * @throws {TypeError} If the value isn't a valid rule options. + */ +function assertIsRuleOptions(value) { + + if (typeof value !== "string" && typeof value !== "number" && !Array.isArray(value)) { + throw new TypeError("Expected a string, number, or array."); + } +} + +/** + * Validates that a value is valid rule severity. + * @param {any} value The value to check. + * @returns {void} + * @throws {TypeError} If the value isn't a valid rule severity. + */ +function assertIsRuleSeverity(value) { + const severity = typeof value === "string" + ? ruleSeverities.get(value.toLowerCase()) + : ruleSeverities.get(value); + + if (typeof severity === "undefined") { + throw new TypeError("Expected severity of \"off\", 0, \"warn\", 1, \"error\", or 2."); + } +} + +/** + * Validates that a given string is the form pluginName/objectName. + * @param {string} value The string to check. + * @returns {void} + * @throws {TypeError} If the string isn't in the correct format. + */ +function assertIsPluginMemberName(value) { + if (!/[@a-z0-9-_$]+(?:\/(?:[a-z0-9-_$]+))+$/iu.test(value)) { + throw new TypeError(`Expected string in the form "pluginName/objectName" but found "${value}".`); + } +} + +/** + * Validates that a value is an object. + * @param {any} value The value to check. + * @returns {void} + * @throws {TypeError} If the value isn't an object. + */ +function assertIsObject(value) { + if (!isNonNullObject(value)) { + throw new TypeError("Expected an object."); + } +} + +/** + * Validates that a value is an object or a string. + * @param {any} value The value to check. + * @returns {void} + * @throws {TypeError} If the value isn't an object or a string. + */ +function assertIsObjectOrString(value) { + if ((!value || typeof value !== "object") && typeof value !== "string") { + throw new TypeError("Expected an object or string."); + } +} + +//----------------------------------------------------------------------------- +// Low-Level Schemas +//----------------------------------------------------------------------------- + + +/** @type {ObjectPropertySchema} */ +const numberSchema = { + merge: "replace", + validate: "number" +}; + +/** @type {ObjectPropertySchema} */ +const booleanSchema = { + merge: "replace", + validate: "boolean" +}; + +/** @type {ObjectPropertySchema} */ +const deepObjectAssignSchema = { + merge(first = {}, second = {}) { + return deepMerge(first, second); + }, + validate: "object" +}; + +//----------------------------------------------------------------------------- +// High-Level Schemas +//----------------------------------------------------------------------------- + +/** @type {ObjectPropertySchema} */ +const globalsSchema = { + merge: "assign", + validate(value) { + + assertIsObject(value); + + for (const key of Object.keys(value)) { + + // avoid hairy edge case + if (key === "__proto__") { + continue; + } + + if (key !== key.trim()) { + throw new TypeError(`Global "${key}" has leading or trailing whitespace.`); + } + + if (!globalVariablesValues.has(value[key])) { + throw new TypeError(`Key "${key}": Expected "readonly", "writable", or "off".`); + } + } + } +}; + +/** @type {ObjectPropertySchema} */ +const parserSchema = { + merge: "replace", + validate(value) { + assertIsObjectOrString(value); + + if (typeof value === "object" && typeof value.parse !== "function" && typeof value.parseForESLint !== "function") { + throw new TypeError("Expected object to have a parse() or parseForESLint() method."); + } + + if (typeof value === "string") { + assertIsPluginMemberName(value); + } + } +}; + +/** @type {ObjectPropertySchema} */ +const pluginsSchema = { + merge(first = {}, second = {}) { + const keys = new Set([...Object.keys(first), ...Object.keys(second)]); + const result = {}; + + // manually validate that plugins are not redefined + for (const key of keys) { + + // avoid hairy edge case + if (key === "__proto__") { + continue; + } + + if (key in first && key in second && first[key] !== second[key]) { + throw new TypeError(`Cannot redefine plugin "${key}".`); + } + + result[key] = second[key] || first[key]; + } + + return result; + }, + validate(value) { + + // first check the value to be sure it's an object + if (value === null || typeof value !== "object") { + throw new TypeError("Expected an object."); + } + + // second check the keys to make sure they are objects + for (const key of Object.keys(value)) { + + // avoid hairy edge case + if (key === "__proto__") { + continue; + } + + if (value[key] === null || typeof value[key] !== "object") { + throw new TypeError(`Key "${key}": Expected an object.`); + } + } + } +}; + +/** @type {ObjectPropertySchema} */ +const processorSchema = { + merge: "replace", + validate(value) { + if (typeof value === "string") { + assertIsPluginMemberName(value); + } else if (value && typeof value === "object") { + if (typeof value.preprocess !== "function" || typeof value.postprocess !== "function") { + throw new TypeError("Object must have a preprocess() and a postprocess() method."); + } + } else { + throw new TypeError("Expected an object or a string."); + } + } +}; + +/** @type {ObjectPropertySchema} */ +const rulesSchema = { + merge(first = {}, second = {}) { + + const result = { + ...first, + ...second + }; + + for (const ruleId of Object.keys(result)) { + + // avoid hairy edge case + if (ruleId === "__proto__") { + + /* eslint-disable-next-line no-proto -- Though deprecated, may still be present */ + delete result.__proto__; + continue; + } + + result[ruleId] = normalizeRuleOptions(result[ruleId]); + + /* + * If either rule config is missing, then the correct + * config is already present and we just need to normalize + * the severity. + */ + if (!(ruleId in first) || !(ruleId in second)) { + continue; + } + + const firstRuleOptions = normalizeRuleOptions(first[ruleId]); + const secondRuleOptions = normalizeRuleOptions(second[ruleId]); + + /* + * If the second rule config only has a severity (length of 1), + * then use that severity and keep the rest of the options from + * the first rule config. + */ + if (secondRuleOptions.length === 1) { + result[ruleId] = [secondRuleOptions[0], ...firstRuleOptions.slice(1)]; + continue; + } + + /* + * In any other situation, then the second rule config takes + * precedence. That means the value at `result[ruleId]` is + * already correct and no further work is necessary. + */ + } + + return result; + }, + + validate(value) { + assertIsObject(value); + + let lastRuleId; + + // Performance: One try-catch has less overhead than one per loop iteration + try { + + /* + * We are not checking the rule schema here because there is no + * guarantee that the rule definition is present at this point. Instead + * we wait and check the rule schema during the finalization step + * of calculating a config. + */ + for (const ruleId of Object.keys(value)) { + + // avoid hairy edge case + if (ruleId === "__proto__") { + continue; + } + + lastRuleId = ruleId; + + const ruleOptions = value[ruleId]; + + assertIsRuleOptions(ruleOptions); + + if (Array.isArray(ruleOptions)) { + assertIsRuleSeverity(ruleOptions[0]); + } else { + assertIsRuleSeverity(ruleOptions); + } + } + } catch (error) { + error.message = `Key "${lastRuleId}": ${error.message}`; + throw error; + } + } +}; + +/** @type {ObjectPropertySchema} */ +const sourceTypeSchema = { + merge: "replace", + validate(value) { + if (typeof value !== "string" || !/^(?:script|module|commonjs)$/u.test(value)) { + throw new TypeError("Expected \"script\", \"module\", or \"commonjs\"."); + } + } +}; + +//----------------------------------------------------------------------------- +// Full schema +//----------------------------------------------------------------------------- + +exports.flatConfigSchema = { + settings: deepObjectAssignSchema, + linterOptions: { + schema: { + noInlineConfig: booleanSchema, + reportUnusedDisableDirectives: booleanSchema + } + }, + languageOptions: { + schema: { + ecmaVersion: numberSchema, + sourceType: sourceTypeSchema, + globals: globalsSchema, + parser: parserSchema, + parserOptions: deepObjectAssignSchema + } + }, + processor: processorSchema, + plugins: pluginsSchema, + rules: rulesSchema +}; diff --git a/eslint/lib/config/rule-validator.js b/eslint/lib/config/rule-validator.js new file mode 100644 index 0000000..527a56e --- /dev/null +++ b/eslint/lib/config/rule-validator.js @@ -0,0 +1,186 @@ +/** + * @fileoverview Rule Validator + * @author Nicholas C. Zakas + */ + +"use strict"; + +//----------------------------------------------------------------------------- +// Requirements +//----------------------------------------------------------------------------- + +const ajv = require("../shared/ajv")(); + +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + +/** + * Finds a rule with the given ID in the given config. + * @param {string} ruleId The ID of the rule to find. + * @param {Object} config The config to search in. + * @throws {TypeError} For missing plugin or rule. + * @returns {{create: Function, schema: (Array|null)}} THe rule object. + */ +function findRuleDefinition(ruleId, config) { + const ruleIdParts = ruleId.split("/"); + let pluginName, ruleName; + + // built-in rule + if (ruleIdParts.length === 1) { + pluginName = "@"; + ruleName = ruleIdParts[0]; + } else { + ruleName = ruleIdParts.pop(); + pluginName = ruleIdParts.join("/"); + } + + const errorMessageHeader = `Key "rules": Key "${ruleId}"`; + let errorMessage = `${errorMessageHeader}: Could not find plugin "${pluginName}".`; + + // if the plugin exists then we need to check if the rule exists + if (config.plugins && config.plugins[pluginName]) { + + const plugin = config.plugins[pluginName]; + + // first check for exact rule match + if (plugin.rules && plugin.rules[ruleName]) { + return config.plugins[pluginName].rules[ruleName]; + } + + errorMessage = `${errorMessageHeader}: Could not find "${ruleName}" in plugin "${pluginName}".`; + + // otherwise, let's see if we can find the rule name elsewhere + for (const [otherPluginName, otherPlugin] of Object.entries(config.plugins)) { + if (otherPlugin.rules && otherPlugin.rules[ruleName]) { + errorMessage += ` Did you mean "${otherPluginName}/${ruleName}"?`; + break; + } + } + + // falls through to throw error + } + + throw new TypeError(errorMessage); +} + +/** + * Gets a complete options schema for a rule. + * @param {{create: Function, schema: (Array|null)}} rule A new-style rule object + * @returns {Object} JSON Schema for the rule's options. + */ +function getRuleOptionsSchema(rule) { + + if (!rule) { + return null; + } + + const schema = rule.schema || rule.meta && rule.meta.schema; + + if (Array.isArray(schema)) { + if (schema.length) { + return { + type: "array", + items: schema, + minItems: 0, + maxItems: schema.length + }; + } + return { + type: "array", + minItems: 0, + maxItems: 0 + }; + + } + + // Given a full schema, leave it alone + return schema || null; +} + +//----------------------------------------------------------------------------- +// Exports +//----------------------------------------------------------------------------- + +/** + * Implements validation functionality for the rules portion of a config. + */ +class RuleValidator { + + /** + * Creates a new instance. + */ + constructor() { + + /** + * A collection of compiled validators for rules that have already + * been validated. + * @type {WeakMap} + */ + this.validators = new WeakMap(); + } + + /** + * Validates all of the rule configurations in a config against each + * rule's schema. + * @param {Object} config The full config to validate. This object must + * contain both the rules section and the plugins section. + * @returns {void} + * @throws {Error} If a rule's configuration does not match its schema. + */ + validate(config) { + + if (!config.rules) { + return; + } + + for (const [ruleId, ruleOptions] of Object.entries(config.rules)) { + + // check for edge case + if (ruleId === "__proto__") { + continue; + } + + /* + * If a rule is disabled, we don't do any validation. This allows + * users to safely set any value to 0 or "off" without worrying + * that it will cause a validation error. + * + * Note: ruleOptions is always an array at this point because + * this validation occurs after FlatConfigArray has merged and + * normalized values. + */ + if (ruleOptions[0] === 0) { + continue; + } + + const rule = findRuleDefinition(ruleId, config); + + // Precompile and cache validator the first time + if (!this.validators.has(rule)) { + const schema = getRuleOptionsSchema(rule); + + if (schema) { + this.validators.set(rule, ajv.compile(schema)); + } + } + + const validateRule = this.validators.get(rule); + + if (validateRule) { + + validateRule(ruleOptions.slice(1)); + + if (validateRule.errors) { + throw new Error(`Key "rules": Key "${ruleId}": ${ + validateRule.errors.map( + error => `\tValue ${JSON.stringify(error.data)} ${error.message}.\n` + ).join("") + }`); + } + } + } + } +} + +exports.RuleValidator = RuleValidator; diff --git a/eslint/lib/eslint/eslint.js b/eslint/lib/eslint/eslint.js index ae2d210..8886d45 100644 --- a/eslint/lib/eslint/eslint.js +++ b/eslint/lib/eslint/eslint.js @@ -54,7 +54,7 @@ const { version } = require("../../package.json"); * @property {string} [ignorePath] The ignore file to use instead of .eslintignore. * @property {ConfigData} [overrideConfig] Override config object, overrides all configs used with this instance * @property {string} [overrideConfigFile] The configuration file to use. - * @property {Record} [plugins] An array of plugin implementations. + * @property {Record|null} [plugins] Preloaded plugins. This is a map-like object, keys are plugin IDs and each value is implementation. * @property {"error" | "warn" | "off"} [reportUnusedDisableDirectives] the severity to report unused eslint-disable directives. * @property {string} [resolvePluginsRelativeTo] The folder where plugins should be resolved from, defaulting to the CWD. * @property {string[]} [rulePaths] An array of directories to load custom rules from. @@ -125,7 +125,7 @@ function isArrayOfNonEmptyString(x) { * @returns {boolean} `true` if `x` is valid fix type. */ function isFixType(x) { - return x === "problem" || x === "suggestion" || x === "layout"; + return x === "directive" || x === "problem" || x === "suggestion" || x === "layout"; } /** @@ -151,6 +151,7 @@ class ESLintInvalidOptionsError extends Error { /** * Validates and normalizes options for the wrapped CLIEngine instance. * @param {ESLintOptions} options The options to process. + * @throws {ESLintInvalidOptionsError} If of any of a variety of type errors. * @returns {ESLintOptions} The normalized options. */ function processOptions({ @@ -237,7 +238,7 @@ function processOptions({ errors.push("'fix' must be a boolean or a function."); } if (fixTypes !== null && !isFixTypeArray(fixTypes)) { - errors.push("'fixTypes' must be an array of any of \"problem\", \"suggestion\", and \"layout\"."); + errors.push("'fixTypes' must be an array of any of \"directive\", \"problem\", \"suggestion\", and \"layout\"."); } if (typeof globInputPaths !== "boolean") { errors.push("'globInputPaths' must be a boolean."); @@ -421,6 +422,9 @@ function compareResultsByFilePath(a, b) { return 0; } +/** + * Main API. + */ class ESLint { /** @@ -429,26 +433,13 @@ class ESLint { */ constructor(options = {}) { const processedOptions = processOptions(options); - const cliEngine = new CLIEngine(processedOptions); + const cliEngine = new CLIEngine(processedOptions, { preloadedPlugins: options.plugins }); const { - additionalPluginPool, configArrayFactory, lastConfigArrays } = getCLIEngineInternalSlots(cliEngine); let updated = false; - /* - * Address `plugins` to add plugin implementations. - * Operate the `additionalPluginPool` internal slot directly to avoid - * using `addPlugin(id, plugin)` method that resets cache everytime. - */ - if (options.plugins) { - for (const [id, plugin] of Object.entries(options.plugins)) { - additionalPluginPool.set(id, plugin); - updated = true; - } - } - /* * Address `overrideConfig` to set override config. * Operate the `configArrayFactory` internal slot directly because this @@ -514,6 +505,39 @@ class ESLint { return CLIEngine.getErrorResults(results); } + /** + * Returns meta objects for each rule represented in the lint results. + * @param {LintResult[]} results The results to fetch rules meta for. + * @returns {Object} A mapping of ruleIds to rule meta objects. + */ + getRulesMetaForResults(results) { + + const resultRuleIds = new Set(); + + // first gather all ruleIds from all results + + for (const result of results) { + for (const { ruleId } of result.messages) { + resultRuleIds.add(ruleId); + } + } + + // create a map of all rules in the results + + const { cliEngine } = privateMembersMap.get(this); + const rules = cliEngine.getRules(); + const resultRules = new Map(); + + for (const [ruleId, rule] of rules) { + if (resultRuleIds.has(ruleId)) { + resultRules.set(ruleId, rule); + } + } + + return createRulesMeta(resultRules); + + } + /** * Executes the current configuration on an array of file and directory names. * @param {string[]} patterns An array of file and directory names. @@ -552,9 +576,12 @@ class ESLint { ...unknownOptions } = options || {}; - for (const key of Object.keys(unknownOptions)) { - throw new Error(`'options' must not include the unknown option '${key}'`); + const unknownOptionKeys = Object.keys(unknownOptions); + + if (unknownOptionKeys.length > 0) { + throw new Error(`'options' must not include the unknown option(s): ${unknownOptionKeys.join(", ")}`); } + if (filePath !== void 0 && !isNonEmptyString(filePath)) { throw new Error("'options.filePath' must be a non-empty string or undefined"); } diff --git a/eslint/lib/init/autoconfig.js b/eslint/lib/init/autoconfig.js index 054c538..ea25234 100644 --- a/eslint/lib/init/autoconfig.js +++ b/eslint/lib/init/autoconfig.js @@ -11,7 +11,11 @@ const equal = require("fast-deep-equal"), recConfig = require("../../conf/eslint-recommended"), - ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"), + { + Legacy: { + ConfigOps + } + } = require("@eslint/eslintrc"), { Linter } = require("../linter"), configRule = require("./config-rule"); @@ -32,9 +36,9 @@ const MAX_CONFIG_COMBINATIONS = 17, // 16 combinations + 1 for severity only /** * Information about a rule configuration, in the context of a Registry. * @typedef {Object} registryItem - * @param {ruleConfig} config A valid configuration for the rule - * @param {number} specificity The number of elements in the ruleConfig array - * @param {number} errorCount The number of errors encountered when linting with the config + * @property {ruleConfig} config A valid configuration for the rule + * @property {number} specificity The number of elements in the ruleConfig array + * @property {number} errorCount The number of errors encountered when linting with the config */ /** @@ -45,8 +49,8 @@ const MAX_CONFIG_COMBINATIONS = 17, // 16 combinations + 1 for severity only /** * Create registryItems for rules - * @param {rulesConfig} rulesConfig Hash of rule names and arrays of ruleConfig items - * @returns {Object} registryItems for each rule in provided rulesConfig + * @param {rulesConfig} rulesConfig Hash of rule names and arrays of ruleConfig items + * @returns {Object} registryItems for each rule in provided rulesConfig */ function makeRegistryItems(rulesConfig) { return Object.keys(rulesConfig).reduce((accumulator, ruleId) => { @@ -69,7 +73,6 @@ function makeRegistryItems(rulesConfig) { */ class Registry { - // eslint-disable-next-line jsdoc/require-description /** * @param {rulesConfig} [rulesConfig] Hash of rule names and arrays of possible configurations */ @@ -100,7 +103,7 @@ class Registry { * configurations. * * The length of the returned array will be <= MAX_CONFIG_COMBINATIONS. - * @returns {Object[]} "rules" configurations to use for linting + * @returns {Object[]} "rules" configurations to use for linting */ buildRuleSets() { let idx = 0; @@ -112,7 +115,7 @@ class Registry { * * This is broken out into its own function so that it doesn't need to be * created inside of the while loop. - * @param {string} rule The ruleId to add. + * @param {string} rule The ruleId to add. * @returns {void} */ const addRuleToRuleSet = function(rule) { @@ -199,7 +202,7 @@ class Registry { * Creates a registry of rules which had no error-free configs. * The new registry is intended to be analyzed to determine whether its rules * should be disabled or set to warning. - * @returns {Registry} A registry of failing rules. + * @returns {Registry} A registry of failing rules. */ getFailingRulesRegistry() { const ruleIds = Object.keys(this.rules), @@ -236,8 +239,8 @@ class Registry { /** * Return a cloned registry containing only configs with a desired specificity - * @param {number} specificity Only keep configs with this specificity - * @returns {Registry} A registry of rules + * @param {number} specificity Only keep configs with this specificity + * @returns {Registry} A registry of rules */ filterBySpecificity(specificity) { const ruleIds = Object.keys(this.rules), @@ -253,10 +256,10 @@ class Registry { /** * Lint SourceCodes against all configurations in the registry, and record results - * @param {Object[]} sourceCodes SourceCode objects for each filename - * @param {Object} config ESLint config object - * @param {progressCallback} [cb] Optional callback for reporting execution status - * @returns {Registry} New registry with errorCount populated + * @param {Object[]} sourceCodes SourceCode objects for each filename + * @param {Object} config ESLint config object + * @param {progressCallback} [cb] Optional callback for reporting execution status + * @returns {Registry} New registry with errorCount populated */ lintSourceCode(sourceCodes, config, cb) { let lintedRegistry = new Registry(); @@ -301,7 +304,7 @@ class Registry { ruleSetIdx += 1; if (cb) { - cb(totalFilesLinting); // eslint-disable-line node/callback-return + cb(totalFilesLinting); // eslint-disable-line node/callback-return -- End of function } }); @@ -318,8 +321,8 @@ class Registry { * * This will return a new config with `["extends": [ ..., "eslint:recommended"]` and * only the rules which have configurations different from the recommended config. - * @param {Object} config config object - * @returns {Object} config object using `"extends": ["eslint:recommended"]` + * @param {Object} config config object + * @returns {Object} config object using `"extends": ["eslint:recommended"]` */ function extendFromRecommended(config) { const newConfig = Object.assign({}, config); diff --git a/eslint/lib/init/config-file.js b/eslint/lib/init/config-file.js index 4c648ac..9eb10fa 100644 --- a/eslint/lib/init/config-file.js +++ b/eslint/lib/init/config-file.js @@ -23,9 +23,9 @@ const debug = require("debug")("eslint:config-file"); * Determines sort order for object keys for json-stable-stringify * * see: https://github.com/samn/json-stable-stringify#cmp - * @param {Object} a The first comparison object ({key: akey, value: avalue}) - * @param {Object} b The second comparison object ({key: bkey, value: bvalue}) - * @returns {number} 1 or -1, used in stringify cmp method + * @param {Object} a The first comparison object ({key: akey, value: avalue}) + * @param {Object} b The second comparison object ({key: bkey, value: bvalue}) + * @returns {number} 1 or -1, used in stringify cmp method */ function sortByKey(a, b) { return a.key > b.key ? 1 : -1; @@ -63,7 +63,7 @@ function writeYAMLConfigFile(config, filePath) { // lazy load YAML to improve performance when not used const yaml = require("js-yaml"); - const content = yaml.safeDump(config, { sortKeys: true }); + const content = yaml.dump(config, { sortKeys: true }); fs.writeFileSync(filePath, content, "utf8"); } diff --git a/eslint/lib/init/config-initializer.js b/eslint/lib/init/config-initializer.js index 3c7f2ba..3c244b7 100644 --- a/eslint/lib/init/config-initializer.js +++ b/eslint/lib/init/config-initializer.js @@ -18,9 +18,13 @@ const util = require("util"), semver = require("semver"), espree = require("espree"), recConfig = require("../../conf/eslint-recommended"), - ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"), + { + Legacy: { + ConfigOps, + naming + } + } = require("@eslint/eslintrc"), log = require("../shared/logging"), - naming = require("@eslint/eslintrc/lib/shared/naming"), ModuleResolver = require("../shared/relative-module-resolver"), autoconfig = require("./autoconfig.js"), ConfigFile = require("./config-file"), @@ -98,8 +102,8 @@ getPeerDependencies.cache = new Map(); /** * Return necessary plugins, configs, parsers, etc. based on the config - * @param {Object} config config object - * @param {boolean} [installESLint=true] If `false` is given, it does not install eslint. + * @param {Object} config config object + * @param {boolean} [installESLint=true] If `false` is given, it does not install eslint. * @returns {string[]} An array of modules to be installed. */ function getModulesList(config, installESLint) { @@ -157,9 +161,10 @@ function getModulesList(config, installESLint) { * * Note: This clones the config object and returns a new config to avoid mutating * the original config parameter. - * @param {Object} answers answers received from enquirer - * @param {Object} config config object - * @returns {Object} config object with configured rules + * @param {Object} answers answers received from enquirer + * @param {Object} config config object + * @throws {Error} If source code retrieval fails or source code file count is 0. + * @returns {Object} config object with configured rules */ function configureRules(answers, config) { const BAR_TOTAL = 20, @@ -411,7 +416,7 @@ function hasESLintVersionConflict(answers) { /** * Install modules. - * @param {string[]} modules Modules to be installed. + * @param {string[]} modules Modules to be installed. * @returns {void} */ function installModules(modules) { @@ -422,9 +427,9 @@ function installModules(modules) { /* istanbul ignore next: no need to test enquirer */ /** * Ask user to install modules. - * @param {string[]} modules Array of modules to be installed. - * @param {boolean} packageJsonExists Indicates if package.json is existed. - * @returns {Promise} Answer that indicates if user wants to install. + * @param {string[]} modules Array of modules to be installed. + * @param {boolean} packageJsonExists Indicates if package.json is existed. + * @returns {Promise} Answer that indicates if user wants to install. */ function askInstallModules(modules, packageJsonExists) { @@ -460,7 +465,7 @@ function askInstallModules(modules, packageJsonExists) { /* istanbul ignore next: no need to test enquirer */ /** * Ask use a few questions on command prompt - * @returns {Promise} The promise with the result of the prompt + * @returns {Promise} The promise with the result of the prompt */ function promptUser() { diff --git a/eslint/lib/init/config-rule.js b/eslint/lib/init/config-rule.js index 7aec89c..131e84a 100644 --- a/eslint/lib/init/config-rule.js +++ b/eslint/lib/init/config-rule.js @@ -17,8 +17,8 @@ const builtInRules = require("../rules"); /** * Wrap all of the elements of an array into arrays. - * @param {*[]} xs Any array. - * @returns {Array[]} An array of arrays. + * @param {*[]} xs Any array. + * @returns {Array[]} An array of arrays. */ function explodeArray(xs) { return xs.reduce((accumulator, x) => { @@ -33,9 +33,9 @@ function explodeArray(xs) { * * For example: * combineArrays([a, [b, c]], [x, y]); // -> [[a, x], [a, y], [b, c, x], [b, c, y]] - * @param {Array} arr1 The first array to combine. - * @param {Array} arr2 The second array to combine. - * @returns {Array} A mixture of the elements of the first and second arrays. + * @param {Array} arr1 The first array to combine. + * @param {Array} arr2 The second array to combine. + * @returns {Array} A mixture of the elements of the first and second arrays. */ function combineArrays(arr1, arr2) { const res = []; @@ -70,8 +70,8 @@ function combineArrays(arr1, arr2) { * [{before: true}, {before: false}], * [{after: true}, {after: false}] * ] - * @param {Object[]} objects Array of objects, each with one property/value pair - * @returns {Array[]} Array of arrays of objects grouped by property + * @param {Object[]} objects Array of objects, each with one property/value pair + * @returns {Array[]} Array of arrays of objects grouped by property */ function groupByProperty(objects) { const groupedObj = objects.reduce((accumulator, obj) => { @@ -97,7 +97,7 @@ function groupByProperty(objects) { * Configs may also have one or more additional elements to specify rule * configuration or options. * @typedef {Array|number} ruleConfig - * @param {number} 0 The rule's severity (0, 1, 2). + * @param {number} 0 The rule's severity (0, 1, 2). */ /** @@ -131,9 +131,9 @@ function groupByProperty(objects) { * {before: false, after: true}, * {before: false, after: false} * ] - * @param {Object[]} objArr1 Single key/value objects, all with the same key - * @param {Object[]} objArr2 Single key/value objects, all with another key - * @returns {Object[]} Combined objects for each combination of input properties and values + * @param {Object[]} objArr1 Single key/value objects, all with the same key + * @param {Object[]} objArr2 Single key/value objects, all with another key + * @returns {Object[]} Combined objects for each combination of input properties and values */ function combinePropertyObjects(objArr1, objArr2) { const res = []; @@ -174,7 +174,6 @@ function combinePropertyObjects(objArr1, objArr2) { */ class RuleConfigSet { - // eslint-disable-next-line jsdoc/require-description /** * @param {ruleConfig[]} configs Valid rule configurations */ @@ -206,7 +205,7 @@ class RuleConfigSet { /** * Add rule configs from an array of strings (schema enums) - * @param {string[]} enums Array of valid rule options (e.g. ["always", "never"]) + * @param {string[]} enums Array of valid rule options (e.g. ["always", "never"]) * @returns {void} */ addEnums(enums) { @@ -215,7 +214,7 @@ class RuleConfigSet { /** * Add rule configurations from a schema object - * @param {Object} obj Schema item with type === "object" + * @param {Object} obj Schema item with type === "object" * @returns {boolean} true if at least one schema for the object could be generated, false otherwise */ addObject(obj) { @@ -260,8 +259,8 @@ class RuleConfigSet { /** * Generate valid rule configurations based on a schema object - * @param {Object} schema A rule's schema object - * @returns {Array[]} Valid rule configurations + * @param {Object} schema A rule's schema object + * @returns {Array[]} Valid rule configurations */ function generateConfigsFromSchema(schema) { const configSet = new RuleConfigSet(); diff --git a/eslint/lib/init/npm-utils.js b/eslint/lib/init/npm-utils.js index 35191cc..4a8efe9 100644 --- a/eslint/lib/init/npm-utils.js +++ b/eslint/lib/init/npm-utils.js @@ -21,8 +21,8 @@ const fs = require("fs"), /** * Find the closest package.json file, starting at process.cwd (by default), * and working up to root. - * @param {string} [startDir=process.cwd()] Starting directory - * @returns {string} Absolute path to closest package.json file + * @param {string} [startDir=process.cwd()] Starting directory + * @returns {string} Absolute path to closest package.json file */ function findPackageJson(startDir) { let dir = path.resolve(startDir || process.cwd()); @@ -45,13 +45,12 @@ function findPackageJson(startDir) { /** * Install node modules synchronously and save to devDependencies in package.json - * @param {string|string[]} packages Node module or modules to install + * @param {string|string[]} packages Node module or modules to install * @returns {void} */ function installSyncSaveDev(packages) { const packageList = Array.isArray(packages) ? packages : [packages]; - const npmProcess = spawn.sync("npm", ["i", "--save-dev"].concat(packageList), - { stdio: "inherit" }); + const npmProcess = spawn.sync("npm", ["i", "--save-dev"].concat(packageList), { stdio: "inherit" }); const error = npmProcess.error; if (error && error.code === "ENOENT") { @@ -87,12 +86,13 @@ function fetchPeerDependencies(packageName) { /** * Check whether node modules are include in a project's package.json. - * @param {string[]} packages Array of node module names - * @param {Object} opt Options Object - * @param {boolean} opt.dependencies Set to true to check for direct dependencies - * @param {boolean} opt.devDependencies Set to true to check for development dependencies - * @param {boolean} opt.startdir Directory to begin searching from - * @returns {Object} An object whose keys are the module names + * @param {string[]} packages Array of node module names + * @param {Object} opt Options Object + * @param {boolean} opt.dependencies Set to true to check for direct dependencies + * @param {boolean} opt.devDependencies Set to true to check for development dependencies + * @param {boolean} opt.startdir Directory to begin searching from + * @throws {Error} If cannot find valid `package.json` file. + * @returns {Object} An object whose keys are the module names * and values are booleans indicating installation. */ function check(packages, opt) { @@ -134,9 +134,9 @@ function check(packages, opt) { * package.json. * * Convenience wrapper around check(). - * @param {string[]} packages Array of node modules to check. - * @param {string} rootDir The directory containing a package.json - * @returns {Object} An object whose keys are the module names + * @param {string[]} packages Array of node modules to check. + * @param {string} rootDir The directory containing a package.json + * @returns {Object} An object whose keys are the module names * and values are booleans indicating installation. */ function checkDeps(packages, rootDir) { @@ -148,8 +148,8 @@ function checkDeps(packages, rootDir) { * package.json. * * Convenience wrapper around check(). - * @param {string[]} packages Array of node modules to check. - * @returns {Object} An object whose keys are the module names + * @param {string[]} packages Array of node modules to check. + * @returns {Object} An object whose keys are the module names * and values are booleans indicating installation. */ function checkDevDeps(packages) { @@ -158,7 +158,7 @@ function checkDevDeps(packages) { /** * Check whether package.json is found in current path. - * @param {string} [startDir] Starting directory + * @param {string} [startDir] Starting directory * @returns {boolean} Whether a package.json is found in current path. */ function checkPackageJson(startDir) { diff --git a/eslint/lib/init/source-code-utils.js b/eslint/lib/init/source-code-utils.js index dca6541..08c20e5 100644 --- a/eslint/lib/init/source-code-utils.js +++ b/eslint/lib/init/source-code-utils.js @@ -23,7 +23,7 @@ const { CLIEngine } = require("../cli-engine"); * TODO1: Expose the API that enumerates target files. * TODO2: Extract the creation logic of `SourceCode` from `Linter` class. */ -const { getCLIEngineInternalSlots } = require("../cli-engine/cli-engine"); // eslint-disable-line node/no-restricted-require +const { getCLIEngineInternalSlots } = require("../cli-engine/cli-engine"); // eslint-disable-line node/no-restricted-require -- Todo const debug = require("debug")("eslint:source-code-utils"); @@ -33,9 +33,10 @@ const debug = require("debug")("eslint:source-code-utils"); /** * Get the SourceCode object for a single file - * @param {string} filename The fully resolved filename to get SourceCode from. - * @param {Object} engine A CLIEngine. - * @returns {Array} Array of the SourceCode object representing the file + * @param {string} filename The fully resolved filename to get SourceCode from. + * @param {Object} engine A CLIEngine. + * @throws {Error} Upon fatal errors from execution. + * @returns {Array} Array of the SourceCode object representing the file * and fatal error message. */ function getSourceCodeOfFile(filename, engine) { @@ -97,7 +98,7 @@ function getSourceCodeOfFiles(patterns, options, callback) { sourceCodes[filename] = sourceCode; } if (callback) { - callback(filenames.length); // eslint-disable-line node/callback-return + callback(filenames.length); // eslint-disable-line node/callback-return -- End of function } }); diff --git a/eslint/lib/linter/apply-disable-directives.js b/eslint/lib/linter/apply-disable-directives.js index 0ba69ca..e5f2e52 100644 --- a/eslint/lib/linter/apply-disable-directives.js +++ b/eslint/lib/linter/apply-disable-directives.js @@ -5,6 +5,8 @@ "use strict"; +const escapeRegExp = require("escape-string-regexp"); + /** * Compares the locations of two objects in a source file * @param {{line: number, column: number}} itemA The first object @@ -16,6 +18,177 @@ function compareLocations(itemA, itemB) { return itemA.line - itemB.line || itemA.column - itemB.column; } +/** + * Groups a set of directives into sub-arrays by their parent comment. + * @param {Directive[]} directives Unused directives to be removed. + * @returns {Directive[][]} Directives grouped by their parent comment. + */ +function groupByParentComment(directives) { + const groups = new Map(); + + for (const directive of directives) { + const { unprocessedDirective: { parentComment } } = directive; + + if (groups.has(parentComment)) { + groups.get(parentComment).push(directive); + } else { + groups.set(parentComment, [directive]); + } + } + + return [...groups.values()]; +} + +/** + * Creates removal details for a set of directives within the same comment. + * @param {Directive[]} directives Unused directives to be removed. + * @param {Token} commentToken The backing Comment token. + * @returns {{ description, fix, position }[]} Details for later creation of output Problems. + */ +function createIndividualDirectivesRemoval(directives, commentToken) { + + /* + * `commentToken.value` starts right after `//` or `/*`. + * All calculated offsets will be relative to this index. + */ + const commentValueStart = commentToken.range[0] + "//".length; + + // Find where the list of rules starts. `\S+` matches with the directive name (e.g. `eslint-disable-line`) + const listStartOffset = /^\s*\S+\s+/u.exec(commentToken.value)[0].length; + + /* + * Get the list text without any surrounding whitespace. In order to preserve the original + * formatting, we don't want to change that whitespace. + * + * // eslint-disable-line rule-one , rule-two , rule-three -- comment + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + */ + const listText = commentToken.value + .slice(listStartOffset) // remove directive name and all whitespace before the list + .split(/\s-{2,}\s/u)[0] // remove `-- comment`, if it exists + .trimRight(); // remove all whitespace after the list + + /* + * We can assume that `listText` contains multiple elements. + * Otherwise, this function wouldn't be called - if there is + * only one rule in the list, then the whole comment must be removed. + */ + + return directives.map(directive => { + const { ruleId } = directive; + + const regex = new RegExp(String.raw`(?:^|\s*,\s*)${escapeRegExp(ruleId)}(?:\s*,\s*|$)`, "u"); + const match = regex.exec(listText); + const matchedText = match[0]; + const matchStartOffset = listStartOffset + match.index; + const matchEndOffset = matchStartOffset + matchedText.length; + + const firstIndexOfComma = matchedText.indexOf(","); + const lastIndexOfComma = matchedText.lastIndexOf(","); + + let removalStartOffset, removalEndOffset; + + if (firstIndexOfComma !== lastIndexOfComma) { + + /* + * Since there are two commas, this must one of the elements in the middle of the list. + * Matched range starts where the previous rule name ends, and ends where the next rule name starts. + * + * // eslint-disable-line rule-one , rule-two , rule-three -- comment + * ^^^^^^^^^^^^^^ + * + * We want to remove only the content between the two commas, and also one of the commas. + * + * // eslint-disable-line rule-one , rule-two , rule-three -- comment + * ^^^^^^^^^^^ + */ + removalStartOffset = matchStartOffset + firstIndexOfComma; + removalEndOffset = matchStartOffset + lastIndexOfComma; + + } else { + + /* + * This is either the first element or the last element. + * + * If this is the first element, matched range starts where the first rule name starts + * and ends where the second rule name starts. This is exactly the range we want + * to remove so that the second rule name will start where the first one was starting + * and thus preserve the original formatting. + * + * // eslint-disable-line rule-one , rule-two , rule-three -- comment + * ^^^^^^^^^^^ + * + * Similarly, if this is the last element, we've already matched the range we want to + * remove. The previous rule name will end where the last one was ending, relative + * to the content on the right side. + * + * // eslint-disable-line rule-one , rule-two , rule-three -- comment + * ^^^^^^^^^^^^^ + */ + removalStartOffset = matchStartOffset; + removalEndOffset = matchEndOffset; + } + + return { + description: `'${ruleId}'`, + fix: { + range: [ + commentValueStart + removalStartOffset, + commentValueStart + removalEndOffset + ], + text: "" + }, + position: directive.unprocessedDirective + }; + }); +} + +/** + * Creates a description of deleting an entire unused disable comment. + * @param {Directive[]} directives Unused directives to be removed. + * @param {Token} commentToken The backing Comment token. + * @returns {{ description, fix, position }} Details for later creation of an output Problem. + */ +function createCommentRemoval(directives, commentToken) { + const { range } = commentToken; + const ruleIds = directives.filter(directive => directive.ruleId).map(directive => `'${directive.ruleId}'`); + + return { + description: ruleIds.length <= 2 + ? ruleIds.join(" or ") + : `${ruleIds.slice(0, ruleIds.length - 1).join(", ")}, or ${ruleIds[ruleIds.length - 1]}`, + fix: { + range, + text: " " + }, + position: directives[0].unprocessedDirective + }; +} + +/** + * Parses details from directives to create output Problems. + * @param {Directive[]} allDirectives Unused directives to be removed. + * @returns {{ description, fix, position }[]} Details for later creation of output Problems. + */ +function processUnusedDisableDirectives(allDirectives) { + const directiveGroups = groupByParentComment(allDirectives); + + return directiveGroups.flatMap( + directives => { + const { parentComment } = directives[0].unprocessedDirective; + const remainingRuleIds = new Set(parentComment.ruleIds); + + for (const directive of directives) { + remainingRuleIds.delete(directive.ruleId); + } + + return remainingRuleIds.size + ? createIndividualDirectivesRemoval(directives, parentComment.commentToken) + : [createCommentRemoval(directives, parentComment.commentToken)]; + } + ); +} + /** * This is the same as the exported function, except that it * doesn't handle disable-line and disable-next-line directives, and it always reports unused @@ -82,17 +255,22 @@ function applyDirectives(options) { } } - const unusedDisableDirectives = options.directives - .filter(directive => directive.type === "disable" && !usedDisableDirectives.has(directive)) - .map(directive => ({ + const unusedDisableDirectivesToReport = options.directives + .filter(directive => directive.type === "disable" && !usedDisableDirectives.has(directive)); + + const processed = processUnusedDisableDirectives(unusedDisableDirectivesToReport); + + const unusedDisableDirectives = processed + .map(({ description, fix, position }) => ({ ruleId: null, - message: directive.ruleId - ? `Unused eslint-disable directive (no problems were reported from '${directive.ruleId}').` + message: description + ? `Unused eslint-disable directive (no problems were reported from ${description}).` : "Unused eslint-disable directive (no problems were reported).", - line: directive.unprocessedDirective.line, - column: directive.unprocessedDirective.column, + line: position.line, + column: position.column, severity: options.reportUnusedDisableDirectives === "warn" ? 1 : 2, - nodeType: null + nodeType: null, + ...options.disableFixes ? {} : { fix } })); return { problems, unusedDisableDirectives }; @@ -113,30 +291,17 @@ function applyDirectives(options) { * @param {{ruleId: (string|null), line: number, column: number}[]} options.problems * A list of problems reported by rules, sorted by increasing location in the file, with one-based columns. * @param {"off" | "warn" | "error"} options.reportUnusedDisableDirectives If `"warn"` or `"error"`, adds additional problems for unused directives + * @param {boolean} options.disableFixes If true, it doesn't make `fix` properties. * @returns {{ruleId: (string|null), line: number, column: number}[]} * A list of reported problems that were not disabled by the directive comments. */ -module.exports = ({ directives, problems, reportUnusedDisableDirectives = "off" }) => { +module.exports = ({ directives, disableFixes, problems, reportUnusedDisableDirectives = "off" }) => { const blockDirectives = directives .filter(directive => directive.type === "disable" || directive.type === "enable") .map(directive => Object.assign({}, directive, { unprocessedDirective: directive })) .sort(compareLocations); - /** - * Returns a new array formed by applying a given callback function to each element of the array, and then flattening the result by one level. - * TODO(stephenwade): Replace this with array.flatMap when we drop support for Node v10 - * @param {any[]} array The array to process - * @param {Function} fn The function to use - * @returns {any[]} The result array - */ - function flatMap(array, fn) { - const mapped = array.map(fn); - const flattened = [].concat(...mapped); - - return flattened; - } - - const lineDirectives = flatMap(directives, directive => { + const lineDirectives = directives.flatMap(directive => { switch (directive.type) { case "disable": case "enable": @@ -162,11 +327,13 @@ module.exports = ({ directives, problems, reportUnusedDisableDirectives = "off" const blockDirectivesResult = applyDirectives({ problems, directives: blockDirectives, + disableFixes, reportUnusedDisableDirectives }); const lineDirectivesResult = applyDirectives({ problems: blockDirectivesResult.problems, directives: lineDirectives, + disableFixes, reportUnusedDisableDirectives }); diff --git a/eslint/lib/linter/code-path-analysis/code-path-analyzer.js b/eslint/lib/linter/code-path-analysis/code-path-analyzer.js index 47427c1..2dcc273 100644 --- a/eslint/lib/linter/code-path-analysis/code-path-analyzer.js +++ b/eslint/lib/linter/code-path-analysis/code-path-analyzer.js @@ -29,6 +29,18 @@ function isCaseNode(node) { return Boolean(node.test); } +/** + * Checks if a given node appears as the value of a PropertyDefinition node. + * @param {ASTNode} node THe node to check. + * @returns {boolean} `true` if the node is a PropertyDefinition value, + * false if not. + */ +function isPropertyDefinitionValue(node) { + const parent = node.parent; + + return parent && parent.type === "PropertyDefinition" && parent.value === node; +} + /** * Checks whether the given logical operator is taken into account for the code * path analysis. @@ -138,6 +150,7 @@ function isIdentifierReference(node) { return parent.id !== node; case "Property": + case "PropertyDefinition": case "MethodDefinition": return ( parent.key !== node || @@ -388,29 +401,68 @@ function processCodePathToEnter(analyzer, node) { let state = codePath && CodePath.getState(codePath); const parent = node.parent; + /** + * Creates a new code path and trigger the onCodePathStart event + * based on the currently selected node. + * @param {string} origin The reason the code path was started. + * @returns {void} + */ + function startCodePath(origin) { + if (codePath) { + + // Emits onCodePathSegmentStart events if updated. + forwardCurrentToHead(analyzer, node); + debug.dumpState(node, state, false); + } + + // Create the code path of this scope. + codePath = analyzer.codePath = new CodePath({ + id: analyzer.idGenerator.next(), + origin, + upper: codePath, + onLooped: analyzer.onLooped + }); + state = CodePath.getState(codePath); + + // Emits onCodePathStart events. + debug.dump(`onCodePathStart ${codePath.id}`); + analyzer.emitter.emit("onCodePathStart", codePath, node); + } + + /* + * Special case: The right side of class field initializer is considered + * to be its own function, so we need to start a new code path in this + * case. + */ + if (isPropertyDefinitionValue(node)) { + startCodePath("class-field-initializer"); + + /* + * Intentional fall through because `node` needs to also be + * processed by the code below. For example, if we have: + * + * class Foo { + * a = () => {} + * } + * + * In this case, we also need start a second code path. + */ + + } + switch (node.type) { case "Program": + startCodePath("program"); + break; + case "FunctionDeclaration": case "FunctionExpression": case "ArrowFunctionExpression": - if (codePath) { - - // Emits onCodePathSegmentStart events if updated. - forwardCurrentToHead(analyzer, node); - debug.dumpState(node, state, false); - } - - // Create the code path of this scope. - codePath = analyzer.codePath = new CodePath( - analyzer.idGenerator.next(), - codePath, - analyzer.onLooped - ); - state = CodePath.getState(codePath); + startCodePath("function"); + break; - // Emits onCodePathStart events. - debug.dump(`onCodePathStart ${codePath.id}`); - analyzer.emitter.emit("onCodePathStart", codePath, node); + case "StaticBlock": + startCodePath("class-static-block"); break; case "ChainExpression": @@ -503,6 +555,7 @@ function processCodePathToEnter(analyzer, node) { * @returns {void} */ function processCodePathToExit(analyzer, node) { + const codePath = analyzer.codePath; const state = CodePath.getState(codePath); let dontForward = false; @@ -627,28 +680,39 @@ function processCodePathToExit(analyzer, node) { * @returns {void} */ function postprocess(analyzer, node) { - switch (node.type) { - case "Program": - case "FunctionDeclaration": - case "FunctionExpression": - case "ArrowFunctionExpression": { - let codePath = analyzer.codePath; - // Mark the current path as the final node. - CodePath.getState(codePath).makeFinal(); + /** + * Ends the code path for the current node. + * @returns {void} + */ + function endCodePath() { + let codePath = analyzer.codePath; + + // Mark the current path as the final node. + CodePath.getState(codePath).makeFinal(); - // Emits onCodePathSegmentEnd event of the current segments. - leaveFromCurrentSegment(analyzer, node); + // Emits onCodePathSegmentEnd event of the current segments. + leaveFromCurrentSegment(analyzer, node); - // Emits onCodePathEnd event of this code path. - debug.dump(`onCodePathEnd ${codePath.id}`); - analyzer.emitter.emit("onCodePathEnd", codePath, node); - debug.dumpDot(codePath); + // Emits onCodePathEnd event of this code path. + debug.dump(`onCodePathEnd ${codePath.id}`); + analyzer.emitter.emit("onCodePathEnd", codePath, node); + debug.dumpDot(codePath); - codePath = analyzer.codePath = analyzer.codePath.upper; - if (codePath) { - debug.dumpState(node, CodePath.getState(codePath), true); - } + codePath = analyzer.codePath = analyzer.codePath.upper; + if (codePath) { + debug.dumpState(node, CodePath.getState(codePath), true); + } + + } + + switch (node.type) { + case "Program": + case "FunctionDeclaration": + case "FunctionExpression": + case "ArrowFunctionExpression": + case "StaticBlock": { + endCodePath(); break; } @@ -662,6 +726,27 @@ function postprocess(analyzer, node) { default: break; } + + /* + * Special case: The right side of class field initializer is considered + * to be its own function, so we need to end a code path in this + * case. + * + * We need to check after the other checks in order to close the + * code paths in the correct order for code like this: + * + * + * class Foo { + * a = () => {} + * } + * + * In this case, The ArrowFunctionExpression code path is closed first + * and then we need to close the code path for the PropertyDefinition + * value. + */ + if (isPropertyDefinitionValue(node)) { + endCodePath(); + } } //------------------------------------------------------------------------------ @@ -674,7 +759,6 @@ function postprocess(analyzer, node) { */ class CodePathAnalyzer { - // eslint-disable-next-line jsdoc/require-description /** * @param {EventGenerator} eventGenerator An event generator to wrap. */ diff --git a/eslint/lib/linter/code-path-analysis/code-path-segment.js b/eslint/lib/linter/code-path-analysis/code-path-segment.js index ca96ad3..fad559a 100644 --- a/eslint/lib/linter/code-path-analysis/code-path-segment.js +++ b/eslint/lib/linter/code-path-analysis/code-path-segment.js @@ -33,7 +33,6 @@ function isReachable(segment) { */ class CodePathSegment { - // eslint-disable-next-line jsdoc/require-description /** * @param {string} id An identifier. * @param {CodePathSegment[]} allPrevSegments An array of the previous segments. diff --git a/eslint/lib/linter/code-path-analysis/code-path-state.js b/eslint/lib/linter/code-path-analysis/code-path-state.js index f75e60e..118f70a 100644 --- a/eslint/lib/linter/code-path-analysis/code-path-state.js +++ b/eslint/lib/linter/code-path-analysis/code-path-state.js @@ -219,7 +219,6 @@ function finalizeTestSegmentsOfFor(context, choiceContext, head) { */ class CodePathState { - // eslint-disable-next-line jsdoc/require-description /** * @param {IdGenerator} idGenerator An id generator to generate id for code * path segments. @@ -360,6 +359,7 @@ class CodePathState { /** * Pops the last choice context and finalizes it. + * @throws {Error} (Unreachable.) * @returns {ChoiceContext} The popped context. */ popChoiceContext() { @@ -450,6 +450,7 @@ class CodePathState { /** * Makes a code path segment of the right-hand operand of a logical * expression. + * @throws {Error} (Unreachable.) * @returns {void} */ makeLogicalRight() { @@ -965,6 +966,7 @@ class CodePathState { * `WhileStatement`, `DoWhileStatement`, `ForStatement`, `ForInStatement`, * and `ForStatement`. * @param {string|null} label A label of the node which was triggered. + * @throws {Error} (Unreachable - unknown type.) * @returns {void} */ pushLoopContext(type, label) { @@ -1036,6 +1038,7 @@ class CodePathState { /** * Pops the last context of a loop statement and finalizes it. + * @throws {Error} (Unreachable - unknown type.) * @returns {void} */ popLoopContext() { diff --git a/eslint/lib/linter/code-path-analysis/code-path.js b/eslint/lib/linter/code-path-analysis/code-path.js index 49b37c6..0e66627 100644 --- a/eslint/lib/linter/code-path-analysis/code-path.js +++ b/eslint/lib/linter/code-path-analysis/code-path.js @@ -21,13 +21,15 @@ const IdGenerator = require("./id-generator"); */ class CodePath { - // eslint-disable-next-line jsdoc/require-description /** - * @param {string} id An identifier. - * @param {CodePath|null} upper The code path of the upper function scope. - * @param {Function} onLooped A callback function to notify looping. + * Creates a new instance. + * @param {Object} options Options for the function (see below). + * @param {string} options.id An identifier. + * @param {string} options.origin The type of code path origin. + * @param {CodePath|null} options.upper The code path of the upper function scope. + * @param {Function} options.onLooped A callback function to notify looping. */ - constructor(id, upper, onLooped) { + constructor({ id, origin, upper, onLooped }) { /** * The identifier of this code path. @@ -36,6 +38,13 @@ class CodePath { */ this.id = id; + /** + * The reason that this code path was started. May be "program", + * "function", "class-field-initializer", or "class-static-block". + * @type {string} + */ + this.origin = origin; + /** * The code path of the upper function scope. * @type {CodePath|null} diff --git a/eslint/lib/linter/code-path-analysis/debug-helpers.js b/eslint/lib/linter/code-path-analysis/debug-helpers.js index a4cb99a..ca64862 100644 --- a/eslint/lib/linter/code-path-analysis/debug-helpers.js +++ b/eslint/lib/linter/code-path-analysis/debug-helpers.js @@ -21,7 +21,7 @@ const debug = require("debug")("eslint:code-path"); * @returns {string} Id of the segment. */ /* istanbul ignore next */ -function getId(segment) { // eslint-disable-line jsdoc/require-jsdoc +function getId(segment) { // eslint-disable-line jsdoc/require-jsdoc -- Ignoring return segment.id + (segment.reachable ? "" : "!"); } @@ -115,7 +115,7 @@ module.exports = { const traceMap = Object.create(null); const arrows = this.makeDotArrows(codePath, traceMap); - for (const id in traceMap) { // eslint-disable-line guard-for-in + for (const id in traceMap) { // eslint-disable-line guard-for-in -- Want ability to traverse prototype const segment = traceMap[id]; text += `${id}[`; diff --git a/eslint/lib/linter/code-path-analysis/fork-context.js b/eslint/lib/linter/code-path-analysis/fork-context.js index 2e872b5..04c59b5 100644 --- a/eslint/lib/linter/code-path-analysis/fork-context.js +++ b/eslint/lib/linter/code-path-analysis/fork-context.js @@ -97,7 +97,6 @@ function mergeExtraSegments(context, segments) { */ class ForkContext { - // eslint-disable-next-line jsdoc/require-description /** * @param {IdGenerator} idGenerator An identifier generator for segments. * @param {ForkContext|null} upper An upper fork context. diff --git a/eslint/lib/linter/code-path-analysis/id-generator.js b/eslint/lib/linter/code-path-analysis/id-generator.js index 4cb2e0e..83787a4 100644 --- a/eslint/lib/linter/code-path-analysis/id-generator.js +++ b/eslint/lib/linter/code-path-analysis/id-generator.js @@ -18,7 +18,6 @@ */ class IdGenerator { - // eslint-disable-next-line jsdoc/require-description /** * @param {string} prefix Optional. A prefix of generated ids. */ diff --git a/eslint/lib/linter/config-comment-parser.js b/eslint/lib/linter/config-comment-parser.js index 07bbead..b88c5e6 100644 --- a/eslint/lib/linter/config-comment-parser.js +++ b/eslint/lib/linter/config-comment-parser.js @@ -3,7 +3,7 @@ * @author Nicholas C. Zakas */ -/* eslint-disable class-methods-use-this*/ +/* eslint class-methods-use-this: off -- Methods desired on instance */ "use strict"; //------------------------------------------------------------------------------ @@ -11,7 +11,11 @@ //------------------------------------------------------------------------------ const levn = require("levn"), - ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"); + { + Legacy: { + ConfigOps + } + } = require("@eslint/eslintrc/universal"); const debug = require("debug")("eslint:config-comment-parser"); diff --git a/eslint/lib/linter/linter.js b/eslint/lib/linter/linter.js index e94b507..4e07a25 100644 --- a/eslint/lib/linter/linter.js +++ b/eslint/lib/linter/linter.js @@ -16,11 +16,15 @@ const evk = require("eslint-visitor-keys"), espree = require("espree"), merge = require("lodash.merge"), - BuiltInEnvironments = require("@eslint/eslintrc/conf/environments"), pkg = require("../../package.json"), astUtils = require("../shared/ast-utils"), - ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"), - ConfigValidator = require("@eslint/eslintrc/lib/shared/config-validator"), + { + Legacy: { + ConfigOps, + ConfigValidator, + environments: BuiltInEnvironments + } + } = require("@eslint/eslintrc/universal"), Traverser = require("../shared/traverser"), { SourceCode } = require("../source-code"), CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"), @@ -37,15 +41,17 @@ const const debug = require("debug")("eslint:linter"); const MAX_AUTOFIX_PASSES = 10; const DEFAULT_PARSER_NAME = "espree"; +const DEFAULT_ECMA_VERSION = 5; const commentParser = new ConfigCommentParser(); const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } }; +const parserSymbol = Symbol.for("eslint.RuleTester.parser"); //------------------------------------------------------------------------------ // Typedefs //------------------------------------------------------------------------------ -/** @typedef {InstanceType} ConfigArray */ -/** @typedef {InstanceType} ExtractedConfig */ +/** @typedef {InstanceType} ConfigArray */ +/** @typedef {InstanceType} ExtractedConfig */ /** @typedef {import("../shared/types").ConfigData} ConfigData */ /** @typedef {import("../shared/types").Environment} Environment */ /** @typedef {import("../shared/types").GlobalConf} GlobalConf */ @@ -54,17 +60,19 @@ const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, colum /** @typedef {import("../shared/types").Processor} Processor */ /** @typedef {import("../shared/types").Rule} Rule */ +/* eslint-disable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */ /** * @template T * @typedef {{ [P in keyof T]-?: T[P] }} Required */ +/* eslint-enable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */ /** * @typedef {Object} DisableDirective - * @property {("disable"|"enable"|"disable-line"|"disable-next-line")} type - * @property {number} line - * @property {number} column - * @property {(string|null)} ruleId + * @property {("disable"|"enable"|"disable-line"|"disable-next-line")} type Type of directive + * @property {number} line The line number + * @property {number} column The column number + * @property {(string|null)} ruleId The rule ID */ /** @@ -92,12 +100,12 @@ const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, colum * @typedef {Object} ProcessorOptions * @property {(filename:string, text:string) => boolean} [filterCodeBlock] the * predicate function that selects adopt code blocks. - * @property {Processor["postprocess"]} [postprocess] postprocessor for report + * @property {Processor.postprocess} [postprocess] postprocessor for report * messages. If provided, this should accept an array of the message lists * for each code block returned from the preprocessor, apply a mapping to * the messages as appropriate, and return a one-dimensional array of * messages. - * @property {Processor["preprocess"]} [preprocess] preprocessor for source text. + * @property {Processor.preprocess} [preprocess] preprocessor for source text. * If provided, this should accept a string of source text, and return an * array of code blocks to lint. */ @@ -240,14 +248,14 @@ function createLintingProblem(options) { * Creates a collection of disable directives from a comment * @param {Object} options to create disable directives * @param {("disable"|"enable"|"disable-line"|"disable-next-line")} options.type The type of directive comment - * @param {{line: number, column: number}} options.loc The 0-based location of the comment token + * @param {token} options.commentToken The Comment token * @param {string} options.value The value after the directive in the comment * comment specified no specific rules, so it applies to all rules (e.g. `eslint-disable`) * @param {function(string): {create: Function}} options.ruleMapper A map from rule IDs to defined rules * @returns {Object} Directives and problems from the comment */ function createDisableDirectives(options) { - const { type, loc, value, ruleMapper } = options; + const { commentToken, type, value, ruleMapper } = options; const ruleIds = Object.keys(commentParser.parseListConfig(value)); const directiveRules = ruleIds.length ? ruleIds : [null]; const result = { @@ -255,13 +263,15 @@ function createDisableDirectives(options) { directiveProblems: [] // problems in directives }; + const parentComment = { commentToken, ruleIds }; + for (const ruleId of directiveRules) { // push to directives, if the rule is defined(including null, e.g. /*eslint enable*/) if (ruleId === null || ruleMapper(ruleId) !== null) { - result.directives.push({ type, line: loc.start.line, column: loc.start.column + 1, ruleId }); + result.directives.push({ parentComment, type, line: commentToken.loc.start.line, column: commentToken.loc.start.column + 1, ruleId }); } else { - result.directiveProblems.push(createLintingProblem({ ruleId, loc })); + result.directiveProblems.push(createLintingProblem({ ruleId, loc: commentToken.loc })); } } return result; @@ -342,7 +352,7 @@ function getDirectiveComments(filename, ast, ruleMapper, warnInlineConfig) { case "eslint-disable-next-line": case "eslint-disable-line": { const directiveType = directiveText.slice("eslint-".length); - const options = { type: directiveType, loc: comment.loc, value: directiveValue, ruleMapper }; + const options = { commentToken: comment, type: directiveType, value: directiveValue, ruleMapper }; const { directives, directiveProblems } = createDisableDirectives(options); disableDirectives.push(...directives); @@ -432,10 +442,16 @@ function getDirectiveComments(filename, ast, ruleMapper, warnInlineConfig) { /** * Normalize ECMAScript version from the initial config - * @param {number} ecmaVersion ECMAScript version from the initial config + * @param {Parser} parser The parser which uses this options. + * @param {number} ecmaVersion ECMAScript version from the initial config * @returns {number} normalized ECMAScript version */ -function normalizeEcmaVersion(ecmaVersion) { +function normalizeEcmaVersion(parser, ecmaVersion) { + if ((parser[parserSymbol] || parser) === espree) { + if (ecmaVersion === "latest") { + return espree.latestEcmaVersion; + } + } /* * Calculate ECMAScript edition number from official year version starting with @@ -444,7 +460,7 @@ function normalizeEcmaVersion(ecmaVersion) { return ecmaVersion >= 2015 ? ecmaVersion - 2009 : ecmaVersion; } -const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)\*\//gsu; +const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)(?:\*\/|$)/gsu; /** * Checks whether or not there is a comment which has "eslint-env *" in a given text. @@ -457,10 +473,12 @@ function findEslintEnv(text) { eslintEnvPattern.lastIndex = 0; while ((match = eslintEnvPattern.exec(text)) !== null) { - retv = Object.assign( - retv || {}, - commentParser.parseListConfig(stripDirectiveComment(match[1])) - ); + if (match[0].endsWith("*/")) { + retv = Object.assign( + retv || {}, + commentParser.parseListConfig(stripDirectiveComment(match[1])) + ); + } } return retv; @@ -521,12 +539,13 @@ function normalizeVerifyOptions(providedOptions, config) { /** * Combines the provided parserOptions with the options from environments - * @param {string} parserName The parser name which uses this options. + * @param {Parser} parser The parser which uses this options. * @param {ParserOptions} providedOptions The provided 'parserOptions' key in a config * @param {Environment[]} enabledEnvironments The environments enabled in configuration and with inline comments * @returns {ParserOptions} Resulting parser options after merge */ -function resolveParserOptions(parserName, providedOptions, enabledEnvironments) { +function resolveParserOptions(parser, providedOptions, enabledEnvironments) { + const parserOptionsFromEnv = enabledEnvironments .filter(env => env.parserOptions) .reduce((parserOptions, env) => merge(parserOptions, env.parserOptions), {}); @@ -542,12 +561,7 @@ function resolveParserOptions(parserName, providedOptions, enabledEnvironments) mergedParserOptions.ecmaFeatures = Object.assign({}, mergedParserOptions.ecmaFeatures, { globalReturn: false }); } - /* - * TODO: @aladdin-add - * 1. for a 3rd-party parser, do not normalize parserOptions - * 2. for espree, no need to do this (espree will do it) - */ - mergedParserOptions.ecmaVersion = normalizeEcmaVersion(mergedParserOptions.ecmaVersion); + mergedParserOptions.ecmaVersion = normalizeEcmaVersion(parser, mergedParserOptions.ecmaVersion); return mergedParserOptions; } @@ -606,13 +620,13 @@ function getRuleOptions(ruleConfig) { */ function analyzeScope(ast, parserOptions, visitorKeys) { const ecmaFeatures = parserOptions.ecmaFeatures || {}; - const ecmaVersion = parserOptions.ecmaVersion || 5; + const ecmaVersion = parserOptions.ecmaVersion || DEFAULT_ECMA_VERSION; return eslintScope.analyze(ast, { ignoreEval: true, nodejsScope: ecmaFeatures.globalReturn, impliedStrict: ecmaFeatures.impliedStrict, - ecmaVersion, + ecmaVersion: typeof ecmaVersion === "number" ? ecmaVersion : 6, sourceType: parserOptions.sourceType || "script", childVisitorKeys: visitorKeys || evk.KEYS, fallback: Traverser.getKeys @@ -754,6 +768,7 @@ function markVariableAsUsed(scopeManager, currentNode, parserOptions, name) { * Runs a rule, and gets its listeners * @param {Rule} rule A normalized rule with a `create` method * @param {Context} ruleContext The context that should be passed to the rule + * @throws {any} Any error during the rule's `create` * @returns {Object} A map of selector listeners provided by the rule */ function createRuleListeners(rule, ruleContext) { @@ -921,8 +936,16 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parser } const problem = reportTranslator(...args); - if (problem.fix && rule.meta && !rule.meta.fixable) { - throw new Error("Fixable rules should export a `meta.fixable` property."); + if (problem.fix && !(rule.meta && rule.meta.fixable)) { + throw new Error("Fixable rules must set the `meta.fixable` property to \"code\" or \"whitespace\"."); + } + if (problem.suggestions && !(rule.meta && rule.meta.hasSuggestions === true)) { + if (rule.meta && rule.meta.docs && typeof rule.meta.docs.suggestion !== "undefined") { + + // Encourage migration from the former property name. + throw new Error("Rules with suggestions must set the `meta.hasSuggestions` property to `true`. `meta.docs.suggestion` is ignored by ESLint."); + } + throw new Error("Rules with suggestions must set the `meta.hasSuggestions` property to `true`."); } lintingProblems.push(problem); } @@ -932,13 +955,31 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parser const ruleListeners = createRuleListeners(rule, ruleContext); + /** + * Include `ruleId` in error logs + * @param {Function} ruleListener A rule method that listens for a node. + * @returns {Function} ruleListener wrapped in error handler + */ + function addRuleErrorHandler(ruleListener) { + return function ruleErrorHandler(...listenerArgs) { + try { + return ruleListener(...listenerArgs); + } catch (e) { + e.ruleId = ruleId; + throw e; + } + }; + } + // add all the selectors from the rule as listeners Object.keys(ruleListeners).forEach(selector => { + const ruleListener = timing.enabled + ? timing.time(ruleId, ruleListeners[selector]) + : ruleListeners[selector]; + emitter.on( selector, - timing.enabled - ? timing.time(ruleId, ruleListeners[selector]) - : ruleListeners[selector] + addRuleErrorHandler(ruleListener) ); }); }); @@ -1023,7 +1064,7 @@ function normalizeCwd(cwd) { } // It's more explicit to assign the undefined - // eslint-disable-next-line no-undefined + // eslint-disable-next-line no-undefined -- Consistently returning a value return undefined; } @@ -1046,7 +1087,7 @@ class Linter { /** * Initialize the Linter. * @param {Object} [config] the config object - * @param {string} [config.cwd] path to a directory that should be considered as the current working directory, can be undefined. + * @param {string} [config.cwd] path to a directory that should be considered as the current working directory, can be undefined. */ constructor({ cwd } = {}) { internalSlotsMap.set(this, { @@ -1074,6 +1115,7 @@ class Linter { * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object. * @param {ConfigData} providedConfig An ESLintConfig instance to configure everything. * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked. + * @throws {Error} If during rule execution. * @returns {LintMessage[]} The results as an array of messages or an empty array if no messages. */ _verifyWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) { @@ -1123,7 +1165,7 @@ class Linter { .map(envName => getEnv(slots, envName)) .filter(env => env); - const parserOptions = resolveParserOptions(parserName, config.parserOptions || {}, enabledEnvs); + const parserOptions = resolveParserOptions(parser, config.parserOptions || {}, enabledEnvs); const configuredGlobals = resolveGlobals(config.globals || {}, enabledEnvs); const settings = config.settings || {}; @@ -1199,11 +1241,17 @@ class Linter { debug("Parser Options:", parserOptions); debug("Parser Path:", parserName); debug("Settings:", settings); + + if (err.ruleId) { + err.message += `\nRule: "${err.ruleId}"`; + } + throw err; } return applyDisableDirectives({ directives: commentDirectives.disableDirectives, + disableFixes: options.disableFixes, problems: lintingProblems .concat(commentDirectives.problems) .sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column), @@ -1291,8 +1339,7 @@ class Linter { const text = ensureText(textOrSourceCode); const preprocess = options.preprocess || (rawText => [rawText]); - // TODO(stephenwade): Replace this with array.flat() when we drop support for Node v10 - const postprocess = options.postprocess || (array => [].concat(...array)); + const postprocess = options.postprocess || (messagesList => messagesList.flat()); const filterCodeBlock = options.filterCodeBlock || (blockFilename => blockFilename.endsWith(".js")); diff --git a/eslint/lib/linter/node-event-generator.js b/eslint/lib/linter/node-event-generator.js index 8b619fd..d56bef2 100644 --- a/eslint/lib/linter/node-event-generator.js +++ b/eslint/lib/linter/node-event-generator.js @@ -37,9 +37,7 @@ const esquery = require("esquery"); * @returns {any[]} The union of the input arrays */ function union(...arrays) { - - // TODO(stephenwade): Replace this with arrays.flat() when we drop support for Node v10 - return [...new Set([].concat(...arrays))]; + return [...new Set(arrays.flat())]; } /** @@ -100,6 +98,13 @@ function getPossibleTypes(parsedSelector) { case "adjacent": return getPossibleTypes(parsedSelector.right); + case "class": + if (parsedSelector.name === "function") { + return ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"]; + } + + return null; + default: return null; @@ -239,7 +244,6 @@ function parseSelector(rawSelector) { */ class NodeEventGenerator { - // eslint-disable-next-line jsdoc/require-description /** * @param {SafeEmitter} emitter * An SafeEmitter which is the destination of events. This emitter must already diff --git a/eslint/lib/linter/report-translator.js b/eslint/lib/linter/report-translator.js index 75005c1..781b313 100644 --- a/eslint/lib/linter/report-translator.js +++ b/eslint/lib/linter/report-translator.js @@ -32,18 +32,18 @@ const interpolate = require("./interpolate"); /** * Information about the report * @typedef {Object} ReportInfo - * @property {string} ruleId - * @property {(0|1|2)} severity - * @property {(string|undefined)} message - * @property {(string|undefined)} [messageId] - * @property {number} line - * @property {number} column - * @property {(number|undefined)} [endLine] - * @property {(number|undefined)} [endColumn] - * @property {(string|null)} nodeType - * @property {string} source - * @property {({text: string, range: (number[]|null)}|null)} [fix] - * @property {Array<{text: string, range: (number[]|null)}|null>} [suggestions] + * @property {string} ruleId The rule ID + * @property {(0|1|2)} severity Severity of the error + * @property {(string|undefined)} message The message + * @property {(string|undefined)} [messageId] The message ID + * @property {number} line The line number + * @property {number} column The column number + * @property {(number|undefined)} [endLine] The ending line number + * @property {(number|undefined)} [endColumn] The ending column number + * @property {(string|null)} nodeType Type of node + * @property {string} source Source text + * @property {({text: string, range: (number[]|null)}|null)} [fix] The fix object + * @property {Array<{text: string, range: (number[]|null)}|null>} [suggestions] Suggestion info */ //------------------------------------------------------------------------------ diff --git a/eslint/lib/linter/rules.js b/eslint/lib/linter/rules.js index a153266..647bab6 100644 --- a/eslint/lib/linter/rules.js +++ b/eslint/lib/linter/rules.js @@ -30,6 +30,9 @@ function normalizeRule(rule) { // Public Interface //------------------------------------------------------------------------------ +/** + * A storage for rules. + */ class Rules { constructor() { this._rules = Object.create(null); diff --git a/eslint/lib/linter/safe-emitter.js b/eslint/lib/linter/safe-emitter.js index ab21223..f4837c1 100644 --- a/eslint/lib/linter/safe-emitter.js +++ b/eslint/lib/linter/safe-emitter.js @@ -12,8 +12,8 @@ /** * An event emitter * @typedef {Object} SafeEmitter - * @property {function(eventName: string, listenerFunc: Function): void} on Adds a listener for a given event name - * @property {function(eventName: string, arg1?: any, arg2?: any, arg3?: any)} emit Emits an event with a given name. + * @property {(eventName: string, listenerFunc: Function) => void} on Adds a listener for a given event name + * @property {(eventName: string, arg1?: any, arg2?: any, arg3?: any) => void} emit Emits an event with a given name. * This calls all the listeners that were listening for that name, with `arg1`, `arg2`, and `arg3` as arguments. * @property {function(): string[]} eventNames Gets the list of event names that have registered listeners. */ diff --git a/eslint/lib/linter/source-code-fixer.js b/eslint/lib/linter/source-code-fixer.js index 53dc1dc..15386c9 100644 --- a/eslint/lib/linter/source-code-fixer.js +++ b/eslint/lib/linter/source-code-fixer.js @@ -80,8 +80,8 @@ SourceCodeFixer.applyFixes = function(sourceText, messages, shouldFix) { /** * Try to use the 'fix' from a problem. - * @param {Message} problem The message object to apply fixes from - * @returns {boolean} Whether fix was successfully applied + * @param {Message} problem The message object to apply fixes from + * @returns {boolean} Whether fix was successfully applied */ function attemptFix(problem) { const fix = problem.fix; diff --git a/eslint/lib/linter/timing.js b/eslint/lib/linter/timing.js index 5823030..c9ab01e 100644 --- a/eslint/lib/linter/timing.js +++ b/eslint/lib/linter/timing.js @@ -116,7 +116,7 @@ function display(data) { return ALIGN[index](":", width + extraAlignment, "-"); }).join("|")); - console.log(table.join("\n")); // eslint-disable-line no-console + console.log(table.join("\n")); // eslint-disable-line no-console -- Debugging function } /* istanbul ignore next */ @@ -126,7 +126,7 @@ module.exports = (function() { /** * Time the run - * @param {*} key key from the data object + * @param {any} key key from the data object * @param {Function} fn function to be called * @returns {Function} function to be executed * @private diff --git a/eslint/lib/options.js b/eslint/lib/options.js index 92c140b..2dd186d 100644 --- a/eslint/lib/options.js +++ b/eslint/lib/options.js @@ -32,7 +32,7 @@ const optionator = require("optionator"); * @property {string[]} [ext] Specify JavaScript file extensions * @property {boolean} fix Automatically fix problems * @property {boolean} fixDryRun Automatically fix problems without saving the changes to the file system - * @property {("problem" | "suggestion" | "layout")[]} [fixType] Specify the types of fixes to apply (problem, suggestion, layout) + * @property {("directive" | "problem" | "suggestion" | "layout")[]} [fixType] Specify the types of fixes to apply (directive, problem, suggestion, layout) * @property {string} format Use a specific output format * @property {string[]} [global] Define global variables * @property {boolean} [help] Show help @@ -151,7 +151,7 @@ module.exports = optionator({ { option: "fix-type", type: "Array", - description: "Specify the types of fixes to apply (problem, suggestion, layout)" + description: "Specify the types of fixes to apply (directive, problem, suggestion, layout)" }, { heading: "Ignoring files" @@ -290,6 +290,12 @@ module.exports = optionator({ default: "true", description: "Prevent errors when pattern is unmatched" }, + { + option: "exit-on-fatal-error", + type: "Boolean", + default: "false", + description: "Exit with exit code 2 in case of fatal error" + }, { option: "debug", type: "Boolean", diff --git a/eslint/lib/rule-tester/rule-tester.js b/eslint/lib/rule-tester/rule-tester.js index b08303c..7f590a5 100644 --- a/eslint/lib/rule-tester/rule-tester.js +++ b/eslint/lib/rule-tester/rule-tester.js @@ -4,7 +4,7 @@ */ "use strict"; -/* global describe, it */ +/* eslint-env mocha -- Mocha wrapper */ /* * This is a wrapper around mocha to allow for DRY unittests for eslint @@ -53,6 +53,9 @@ const const ajv = require("../shared/ajv")({ strictDefaults: true }); const espreePath = require.resolve("espree"); +const parserSymbol = Symbol.for("eslint.RuleTester.parser"); + +const { SourceCode } = require("../source-code"); //------------------------------------------------------------------------------ // Typedefs @@ -60,9 +63,11 @@ const espreePath = require.resolve("espree"); /** @typedef {import("../shared/types").Parser} Parser */ +/* eslint-disable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */ /** * A test case that is expected to pass lint. * @typedef {Object} ValidTestCase + * @property {string} [name] Name for the test case. * @property {string} code Code for the test case. * @property {any[]} [options] Options for the test case. * @property {{ [name: string]: any }} [settings] Settings for the test case. @@ -71,11 +76,13 @@ const espreePath = require.resolve("espree"); * @property {{ [name: string]: any }} [parserOptions] Options for the parser. * @property {{ [name: string]: "readonly" | "writable" | "off" }} [globals] The additional global variables. * @property {{ [name: string]: boolean }} [env] Environments for the test case. + * @property {boolean} [only] Run only this test case or the subset of test cases with this property. */ /** * A test case that is expected to fail lint. * @typedef {Object} InvalidTestCase + * @property {string} [name] Name for the test case. * @property {string} code Code for the test case. * @property {number | Array} errors Expected errors. * @property {string | null} [output] The expected code after autofixes are applied. If set to `null`, the test runner will assert that no autofix is suggested. @@ -86,6 +93,7 @@ const espreePath = require.resolve("espree"); * @property {{ [name: string]: any }} [parserOptions] Options for the parser. * @property {{ [name: string]: "readonly" | "writable" | "off" }} [globals] The additional global variables. * @property {{ [name: string]: boolean }} [env] Environments for the test case. + * @property {boolean} [only] Run only this test case or the subset of test cases with this property. */ /** @@ -100,6 +108,7 @@ const espreePath = require.resolve("espree"); * @property {number} [endLine] The 1-based line number of the reported end location. * @property {number} [endColumn] The 1-based column number of the reported end location. */ +/* eslint-enable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */ //------------------------------------------------------------------------------ // Private Members @@ -117,11 +126,13 @@ let defaultConfig = { rules: {} }; * configuration */ const RuleTesterParameters = [ + "name", "code", "filename", "options", "errors", - "output" + "output", + "only" ]; /* @@ -206,7 +217,7 @@ function freezeDeeply(x) { */ function sanitize(text) { return text.replace( - /[\u0000-\u0009\u000b-\u001a]/gu, // eslint-disable-line no-control-regex + /[\u0000-\u0009\u000b-\u001a]/gu, // eslint-disable-line no-control-regex -- Escaping controls c => `\\u${c.codePointAt(0).toString(16).padStart(4, "0")}` ); } @@ -236,6 +247,7 @@ function defineStartEndAsError(objName, node) { }); } + /** * Define `start`/`end` properties of all nodes of the given AST as throwing error. * @param {ASTNode} ast The root node to errorize `start`/`end` properties. @@ -255,8 +267,10 @@ function defineStartEndAsErrorInTree(ast, visitorKeys) { * @returns {Parser} Wrapped parser object. */ function wrapParser(parser) { + if (typeof parser.parseForESLint === "function") { return { + [parserSymbol]: parser, parseForESLint(...args) { const ret = parser.parseForESLint(...args); @@ -265,7 +279,9 @@ function wrapParser(parser) { } }; } + return { + [parserSymbol]: parser, parse(...args) { const ast = parser.parse(...args); @@ -275,6 +291,17 @@ function wrapParser(parser) { }; } +/** + * Function to replace `SourceCode.prototype.getComments`. + * @returns {void} + * @throws {Error} Deprecation message. + */ +function getCommentsDeprecation() { + throw new Error( + "`SourceCode#getComments()` is deprecated and will be removed in a future major version. Use `getCommentsBefore()`, `getCommentsAfter()`, and `getCommentsInside()` instead." + ); +} + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ @@ -282,12 +309,14 @@ function wrapParser(parser) { // default separators for testing const DESCRIBE = Symbol("describe"); const IT = Symbol("it"); +const IT_ONLY = Symbol("itOnly"); /** * This is `it` default handler if `it` don't exist. * @this {Mocha} * @param {string} text The description of the test case. * @param {Function} method The logic of the test case. + * @throws {Error} Any error upon execution of `method`. * @returns {any} Returned value of `method`. */ function itDefaultHandler(text, method) { @@ -312,6 +341,9 @@ function describeDefaultHandler(text, method) { return method.call(this); } +/** + * Mocha test wrapper. + */ class RuleTester { /** @@ -343,6 +375,7 @@ class RuleTester { /** * Set the configuration to use for all future tests * @param {Object} config the configuration to use. + * @throws {TypeError} If non-object config. * @returns {void} */ static setDefaultConfig(config) { @@ -400,6 +433,46 @@ class RuleTester { this[IT] = value; } + /** + * Adds the `only` property to a test to run it in isolation. + * @param {string | ValidTestCase | InvalidTestCase} item A single test to run by itself. + * @returns {ValidTestCase | InvalidTestCase} The test with `only` set. + */ + static only(item) { + if (typeof item === "string") { + return { code: item, only: true }; + } + + return { ...item, only: true }; + } + + static get itOnly() { + if (typeof this[IT_ONLY] === "function") { + return this[IT_ONLY]; + } + if (typeof this[IT] === "function" && typeof this[IT].only === "function") { + return Function.bind.call(this[IT].only, this[IT]); + } + if (typeof it === "function" && typeof it.only === "function") { + return Function.bind.call(it.only, it); + } + + if (typeof this[DESCRIBE] === "function" || typeof this[IT] === "function") { + throw new Error( + "Set `RuleTester.itOnly` to use `only` with a custom test framework.\n" + + "See https://eslint.org/docs/developer-guide/nodejs-api#customizing-ruletester for more." + ); + } + if (typeof it === "function") { + throw new Error("The current test framework does not support exclusive tests with `only`."); + } + throw new Error("To use `only`, use RuleTester with a test framework that provides `it.only()` like Mocha."); + } + + static set itOnly(value) { + this[IT_ONLY] = value; + } + /** * Define a rule for one particular run of tests. * @param {string} name The name of the rule to define. @@ -418,6 +491,8 @@ class RuleTester { * valid: (ValidTestCase | string)[], * invalid: InvalidTestCase[] * }} test The collection of tests to run. + * @throws {TypeError|Error} If non-object `test`, or if a required + * scenario of the given type is missing. * @returns {void} */ run(ruleName, rule, test) { @@ -461,6 +536,7 @@ class RuleTester { /** * Run the rule for the given item * @param {string|Object} item Item to run the rule against + * @throws {Error} If an invalid schema. * @returns {Object} Eslint run result * @private */ @@ -557,7 +633,16 @@ class RuleTester { validate(config, "rule-tester", id => (id === ruleName ? rule : null)); // Verify the code. - const messages = linter.verify(code, config, filename); + const { getComments } = SourceCode.prototype; + let messages; + + try { + SourceCode.prototype.getComments = getCommentsDeprecation; + messages = linter.verify(code, config, filename); + } finally { + SourceCode.prototype.getComments = getComments; + } + const fatalErrorMessage = messages.find(m => m.fatal); assert(!fatalErrorMessage, `A fatal parsing error occurred: ${fatalErrorMessage && fatalErrorMessage.message}`); @@ -610,7 +695,8 @@ class RuleTester { const messages = result.messages; assert.strictEqual(messages.length, 0, util.format("Should have no errors but had %d: %s", - messages.length, util.inspect(messages))); + messages.length, + util.inspect(messages))); assertASTDidntChange(result.beforeAST, result.afterAST); } @@ -665,13 +751,18 @@ class RuleTester { } assert.strictEqual(messages.length, item.errors, util.format("Should have %d error%s but had %d: %s", - item.errors, item.errors === 1 ? "" : "s", messages.length, util.inspect(messages))); + item.errors, + item.errors === 1 ? "" : "s", + messages.length, + util.inspect(messages))); } else { assert.strictEqual( - messages.length, item.errors.length, - util.format( + messages.length, item.errors.length, util.format( "Should have %d error%s but had %d: %s", - item.errors.length, item.errors.length === 1 ? "" : "s", messages.length, util.inspect(messages) + item.errors.length, + item.errors.length === 1 ? "" : "s", + messages.length, + util.inspect(messages) ) ); @@ -865,16 +956,6 @@ 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); } @@ -885,23 +966,29 @@ class RuleTester { RuleTester.describe(ruleName, () => { RuleTester.describe("valid", () => { test.valid.forEach(valid => { - RuleTester.it(sanitize(typeof valid === "object" ? valid.code : valid), () => { - testValidTemplate(valid); - }); + RuleTester[valid.only ? "itOnly" : "it"]( + sanitize(typeof valid === "object" ? valid.name || valid.code : valid), + () => { + testValidTemplate(valid); + } + ); }); }); RuleTester.describe("invalid", () => { test.invalid.forEach(invalid => { - RuleTester.it(sanitize(invalid.code), () => { - testInvalidTemplate(invalid); - }); + RuleTester[invalid.only ? "itOnly" : "it"]( + sanitize(invalid.name || invalid.code), + () => { + testInvalidTemplate(invalid); + } + ); }); }); }); } } -RuleTester[DESCRIBE] = RuleTester[IT] = null; +RuleTester[DESCRIBE] = RuleTester[IT] = RuleTester[IT_ONLY] = null; module.exports = RuleTester; diff --git a/eslint/lib/rules/accessor-pairs.js b/eslint/lib/rules/accessor-pairs.js index 0e0d07a..f047252 100644 --- a/eslint/lib/rules/accessor-pairs.js +++ b/eslint/lib/rules/accessor-pairs.js @@ -140,7 +140,6 @@ module.exports = { docs: { description: "enforce getter and setter pairs in objects and classes", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/accessor-pairs" }, diff --git a/eslint/lib/rules/array-bracket-newline.js b/eslint/lib/rules/array-bracket-newline.js index b4b4dd4..28a05b3 100644 --- a/eslint/lib/rules/array-bracket-newline.js +++ b/eslint/lib/rules/array-bracket-newline.js @@ -17,7 +17,6 @@ module.exports = { docs: { description: "enforce linebreaks after opening and before closing array brackets", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/array-bracket-newline" }, diff --git a/eslint/lib/rules/array-bracket-spacing.js b/eslint/lib/rules/array-bracket-spacing.js index c2b77a6..1eea99c 100644 --- a/eslint/lib/rules/array-bracket-spacing.js +++ b/eslint/lib/rules/array-bracket-spacing.js @@ -16,7 +16,6 @@ module.exports = { docs: { description: "enforce consistent spacing inside array brackets", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/array-bracket-spacing" }, diff --git a/eslint/lib/rules/array-callback-return.js b/eslint/lib/rules/array-callback-return.js index 7267347..d13ecd7 100644 --- a/eslint/lib/rules/array-callback-return.js +++ b/eslint/lib/rules/array-callback-return.js @@ -139,7 +139,6 @@ module.exports = { docs: { description: "enforce `return` statements in callbacks of array methods", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/array-callback-return" }, diff --git a/eslint/lib/rules/array-element-newline.js b/eslint/lib/rules/array-element-newline.js index b7a9678..535fa21 100644 --- a/eslint/lib/rules/array-element-newline.js +++ b/eslint/lib/rules/array-element-newline.js @@ -17,7 +17,6 @@ module.exports = { docs: { description: "enforce line breaks after each array element", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/array-element-newline" }, diff --git a/eslint/lib/rules/arrow-body-style.js b/eslint/lib/rules/arrow-body-style.js index 5b8a5f0..3a3f544 100644 --- a/eslint/lib/rules/arrow-body-style.js +++ b/eslint/lib/rules/arrow-body-style.js @@ -20,7 +20,6 @@ module.exports = { docs: { description: "require braces around arrow function bodies", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/arrow-body-style" }, diff --git a/eslint/lib/rules/arrow-parens.js b/eslint/lib/rules/arrow-parens.js index eaa1aab..4f4dea0 100644 --- a/eslint/lib/rules/arrow-parens.js +++ b/eslint/lib/rules/arrow-parens.js @@ -33,7 +33,6 @@ module.exports = { docs: { description: "require parentheses around arrow function arguments", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/arrow-parens" }, diff --git a/eslint/lib/rules/arrow-spacing.js b/eslint/lib/rules/arrow-spacing.js index e5110c6..9e1ed71 100644 --- a/eslint/lib/rules/arrow-spacing.js +++ b/eslint/lib/rules/arrow-spacing.js @@ -20,7 +20,6 @@ module.exports = { docs: { description: "enforce consistent spacing before and after the arrow in arrow functions", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/arrow-spacing" }, diff --git a/eslint/lib/rules/block-scoped-var.js b/eslint/lib/rules/block-scoped-var.js index 481057b..d98250b 100644 --- a/eslint/lib/rules/block-scoped-var.js +++ b/eslint/lib/rules/block-scoped-var.js @@ -14,7 +14,6 @@ module.exports = { docs: { description: "enforce the use of variables within the scope they are defined", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/block-scoped-var" }, @@ -113,6 +112,8 @@ module.exports = { "SwitchStatement:exit": exitScope, CatchClause: enterScope, "CatchClause:exit": exitScope, + StaticBlock: enterScope, + "StaticBlock:exit": exitScope, // Finds and reports references which are outside of valid scope. VariableDeclaration: checkForVariables diff --git a/eslint/lib/rules/block-spacing.js b/eslint/lib/rules/block-spacing.js index c4b30b0..990b441 100644 --- a/eslint/lib/rules/block-spacing.js +++ b/eslint/lib/rules/block-spacing.js @@ -17,7 +17,6 @@ module.exports = { docs: { description: "disallow or enforce spaces inside of blocks after opening block and before closing block", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/block-spacing" }, @@ -41,7 +40,7 @@ module.exports = { /** * Gets the open brace token from a given node. - * @param {ASTNode} node A BlockStatement/SwitchStatement node to get. + * @param {ASTNode} node A BlockStatement/StaticBlock/SwitchStatement node to get. * @returns {Token} The token of the open brace. */ function getOpenBrace(node) { @@ -51,6 +50,12 @@ module.exports = { } return sourceCode.getLastToken(node, 1); } + + if (node.type === "StaticBlock") { + return sourceCode.getFirstToken(node, { skip: 1 }); // skip the `static` token + } + + // "BlockStatement" return sourceCode.getFirstToken(node); } @@ -73,8 +78,8 @@ module.exports = { } /** - * Reports invalid spacing style inside braces. - * @param {ASTNode} node A BlockStatement/SwitchStatement node to get. + * Checks and reports invalid spacing style inside braces. + * @param {ASTNode} node A BlockStatement/StaticBlock/SwitchStatement node to check. * @returns {void} */ function checkSpacingInsideBraces(node) { @@ -158,6 +163,7 @@ module.exports = { return { BlockStatement: checkSpacingInsideBraces, + StaticBlock: checkSpacingInsideBraces, SwitchStatement: checkSpacingInsideBraces }; } diff --git a/eslint/lib/rules/brace-style.js b/eslint/lib/rules/brace-style.js index 07223d1..89f9ba5 100644 --- a/eslint/lib/rules/brace-style.js +++ b/eslint/lib/rules/brace-style.js @@ -17,7 +17,6 @@ module.exports = { docs: { description: "enforce consistent brace style for blocks", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/brace-style" }, @@ -156,6 +155,12 @@ module.exports = { validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node)); } }, + StaticBlock(node) { + validateCurlyPair( + sourceCode.getFirstToken(node, { skip: 1 }), // skip the `static` token + sourceCode.getLastToken(node) + ); + }, ClassBody(node) { validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node)); }, diff --git a/eslint/lib/rules/callback-return.js b/eslint/lib/rules/callback-return.js index fa66e63..449b9a9 100644 --- a/eslint/lib/rules/callback-return.js +++ b/eslint/lib/rules/callback-return.js @@ -1,6 +1,7 @@ /** * @fileoverview Enforce return after a callback. * @author Jamund Ferguson + * @deprecated in ESLint v7.0.0 */ "use strict"; @@ -18,7 +19,6 @@ module.exports = { docs: { description: "require `return` statements after callbacks", - category: "Node.js and CommonJS", recommended: false, url: "https://eslint.org/docs/rules/callback-return" }, diff --git a/eslint/lib/rules/camelcase.js b/eslint/lib/rules/camelcase.js index d34656c..7e8fc68 100644 --- a/eslint/lib/rules/camelcase.js +++ b/eslint/lib/rules/camelcase.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "enforce camelcase naming convention", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/camelcase" }, @@ -55,32 +54,25 @@ module.exports = { ], messages: { - notCamelCase: "Identifier '{{name}}' is not in camel case." + notCamelCase: "Identifier '{{name}}' is not in camel case.", + notCamelCasePrivate: "#{{name}} is not in camel case." } }, create(context) { - const options = context.options[0] || {}; - let properties = options.properties || ""; + const properties = options.properties === "never" ? "never" : "always"; const ignoreDestructuring = options.ignoreDestructuring; const ignoreImports = options.ignoreImports; const ignoreGlobals = options.ignoreGlobals; const allow = options.allow || []; - let globalScope; - - if (properties !== "always" && properties !== "never") { - properties = "always"; - } - //-------------------------------------------------------------------------- // Helpers //-------------------------------------------------------------------------- // contains reported nodes to avoid reporting twice on destructuring with shorthand notation - const reported = []; - const ALLOWED_PARENT_TYPES = new Set(["CallExpression", "NewExpression"]); + const reported = new Set(); /** * Checks if a string contains an underscore and isn't all upper-case @@ -89,9 +81,10 @@ module.exports = { * @private */ function isUnderscored(name) { + const nameBody = name.replace(/^_+|_+$/gu, ""); // if there's an underscore, it might be A_CONSTANT, which is okay - return name.includes("_") && name !== name.toUpperCase(); + return nameBody.includes("_") && nameBody !== nameBody.toUpperCase(); } /** @@ -107,219 +100,292 @@ module.exports = { } /** - * Checks if a parent of a node is an ObjectPattern. - * @param {ASTNode} node The node to check. - * @returns {boolean} if the node is inside an ObjectPattern + * Checks if a given name is good or not. + * @param {string} name The name to check. + * @returns {boolean} `true` if the name is good. * @private */ - function isInsideObjectPattern(node) { - let current = node; - - while (current) { - const parent = current.parent; + function isGoodName(name) { + return !isUnderscored(name) || isAllowed(name); + } - if (parent && parent.type === "Property" && parent.computed && parent.key === current) { - return false; - } + /** + * Checks if a given identifier reference or member expression is an assignment + * target. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node is an assignment target. + */ + function isAssignmentTarget(node) { + const parent = node.parent; - if (current.type === "ObjectPattern") { + switch (parent.type) { + case "AssignmentExpression": + case "AssignmentPattern": + return parent.left === node; + + case "Property": + return ( + parent.parent.type === "ObjectPattern" && + parent.value === node + ); + case "ArrayPattern": + case "RestElement": return true; - } - current = parent; + default: + return false; } - - return false; } /** - * Checks whether the given node represents assignment target property in destructuring. - * - * For examples: - * ({a: b.foo} = c); // => true for `foo` - * ([a.foo] = b); // => true for `foo` - * ([a.foo = 1] = b); // => true for `foo` - * ({...a.foo} = b); // => true for `foo` - * @param {ASTNode} node An Identifier node to check - * @returns {boolean} True if the node is an assignment target property in destructuring. + * Checks if a given binding identifier uses the original name as-is. + * - If it's in object destructuring, the original name is its property name. + * - If it's in import declaration, the original name is its exported name. + * @param {ASTNode} node The `Identifier` node to check. + * @returns {boolean} `true` if the identifier uses the original name as-is. */ - function isAssignmentTargetPropertyInDestructuring(node) { - if ( - node.parent.type === "MemberExpression" && - node.parent.property === node && - !node.parent.computed - ) { - const effectiveParent = node.parent.parent; - - return ( - effectiveParent.type === "Property" && - effectiveParent.value === node.parent && - effectiveParent.parent.type === "ObjectPattern" || - effectiveParent.type === "ArrayPattern" || - effectiveParent.type === "RestElement" || - ( - effectiveParent.type === "AssignmentPattern" && - effectiveParent.left === node.parent - ) - ); + function equalsToOriginalName(node) { + const localName = node.name; + const valueNode = node.parent.type === "AssignmentPattern" + ? node.parent + : node; + const parent = valueNode.parent; + + switch (parent.type) { + case "Property": + return ( + parent.parent.type === "ObjectPattern" && + parent.value === valueNode && + !parent.computed && + parent.key.type === "Identifier" && + parent.key.name === localName + ); + + case "ImportSpecifier": + return ( + parent.local === node && + parent.imported.name === localName + ); + + default: + return false; } - return false; } /** - * Checks whether the given node represents a reference to a global variable that is not declared in the source code. - * These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables. - * @param {ASTNode} node `Identifier` node to check. - * @returns {boolean} `true` if the node is a reference to a global variable. + * Reports an AST node as a rule violation. + * @param {ASTNode} node The node to report. + * @returns {void} + * @private */ - function isReferenceToGlobalVariable(node) { - const variable = globalScope.set.get(node.name); - - return variable && variable.defs.length === 0 && - variable.references.some(ref => ref.identifier === node); + function report(node) { + if (reported.has(node.range[0])) { + return; + } + reported.add(node.range[0]); + + // Report it. + context.report({ + node, + messageId: node.type === "PrivateIdentifier" + ? "notCamelCasePrivate" + : "notCamelCase", + data: { name: node.name } + }); } /** - * Checks whether the given node represents a reference to a property of an object in an object literal expression. - * This allows to differentiate between a global variable that is allowed to be used as a reference, and the key - * of the expressed object (which shouldn't be allowed). - * @param {ASTNode} node `Identifier` node to check. - * @returns {boolean} `true` if the node is a property name of an object literal expression + * Reports an identifier reference or a binding identifier. + * @param {ASTNode} node The `Identifier` node to report. + * @returns {void} */ - function isPropertyNameInObjectLiteral(node) { - const parent = node.parent; + function reportReferenceId(node) { - return ( - parent.type === "Property" && - parent.parent.type === "ObjectExpression" && - !parent.computed && - parent.key === node - ); - } + /* + * For backward compatibility, if it's in callings then ignore it. + * Not sure why it is. + */ + if ( + node.parent.type === "CallExpression" || + node.parent.type === "NewExpression" + ) { + return; + } - /** - * Reports an AST node as a rule violation. - * @param {ASTNode} node The node to report. - * @returns {void} - * @private - */ - function report(node) { - if (!reported.includes(node)) { - reported.push(node); - context.report({ node, messageId: "notCamelCase", data: { name: node.name } }); + /* + * For backward compatibility, if it's a default value of + * destructuring/parameters then ignore it. + * Not sure why it is. + */ + if ( + node.parent.type === "AssignmentPattern" && + node.parent.right === node + ) { + return; } + + /* + * The `ignoreDestructuring` flag skips the identifiers that uses + * the property name as-is. + */ + if (ignoreDestructuring && equalsToOriginalName(node)) { + return; + } + + report(node); } return { + // Report camelcase of global variable references ------------------ Program() { - globalScope = context.getScope(); - }, - - Identifier(node) { + const scope = context.getScope(); - /* - * Leading and trailing underscores are commonly used to flag - * private/protected identifiers, strip them before checking if underscored - */ - const name = node.name, - nameIsUnderscored = isUnderscored(name.replace(/^_+|_+$/gu, "")), - effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent; + if (!ignoreGlobals) { - // First, we ignore the node if it match the ignore list - if (isAllowed(name)) { - return; - } + // Defined globals in config files or directive comments. + for (const variable of scope.variables) { + if ( + variable.identifiers.length > 0 || + isGoodName(variable.name) + ) { + continue; + } + for (const reference of variable.references) { - // Check if it's a global variable - if (ignoreGlobals && isReferenceToGlobalVariable(node) && !isPropertyNameInObjectLiteral(node)) { - return; + /* + * For backward compatibility, this rule reports read-only + * references as well. + */ + reportReferenceId(reference.identifier); + } + } } - // MemberExpressions get special rules - if (node.parent.type === "MemberExpression") { + // Undefined globals. + for (const reference of scope.through) { + const id = reference.identifier; - // "never" check properties - if (properties === "never") { - return; + if (isGoodName(id.name)) { + continue; } - // Always report underscored object names - if (node.parent.object.type === "Identifier" && node.parent.object.name === node.name && nameIsUnderscored) { - report(node); - - // Report AssignmentExpressions only if they are the left side of the assignment - } else if (effectiveParent.type === "AssignmentExpression" && nameIsUnderscored && (effectiveParent.right.type !== "MemberExpression" || effectiveParent.left.type === "MemberExpression" && effectiveParent.left.property.name === node.name)) { - report(node); + /* + * For backward compatibility, this rule reports read-only + * references as well. + */ + reportReferenceId(id); + } + }, - } else if (isAssignmentTargetPropertyInDestructuring(node) && nameIsUnderscored) { - report(node); + // Report camelcase of declared variables -------------------------- + [[ + "VariableDeclaration", + "FunctionDeclaration", + "FunctionExpression", + "ArrowFunctionExpression", + "ClassDeclaration", + "ClassExpression", + "CatchClause" + ]](node) { + for (const variable of context.getDeclaredVariables(node)) { + if (isGoodName(variable.name)) { + continue; } + const id = variable.identifiers[0]; - /* - * Properties have their own rules, and - * AssignmentPattern nodes can be treated like Properties: - * e.g.: const { no_camelcased = false } = bar; - */ - } else if (node.parent.type === "Property" || node.parent.type === "AssignmentPattern") { - - if (node.parent.parent && node.parent.parent.type === "ObjectPattern") { - if (node.parent.shorthand && node.parent.value.left && nameIsUnderscored) { - report(node); - } - - const assignmentKeyEqualsValue = node.parent.key.name === node.parent.value.name; - - if (nameIsUnderscored && node.parent.computed) { - report(node); - } + // Report declaration. + if (!(ignoreDestructuring && equalsToOriginalName(id))) { + report(id); + } - // prevent checking righthand side of destructured object - if (node.parent.key === node && node.parent.value !== node) { - return; + /* + * For backward compatibility, report references as well. + * It looks unnecessary because declarations are reported. + */ + for (const reference of variable.references) { + if (reference.init) { + continue; // Skip the write references of initializers. } + reportReferenceId(reference.identifier); + } + } + }, - const valueIsUnderscored = node.parent.value.name && nameIsUnderscored; + // Report camelcase in properties ---------------------------------- + [[ + "ObjectExpression > Property[computed!=true] > Identifier.key", + "MethodDefinition[computed!=true] > Identifier.key", + "PropertyDefinition[computed!=true] > Identifier.key", + "MethodDefinition > PrivateIdentifier.key", + "PropertyDefinition > PrivateIdentifier.key" + ]](node) { + if (properties === "never" || isGoodName(node.name)) { + return; + } + report(node); + }, + "MemberExpression[computed!=true] > Identifier.property"(node) { + if ( + properties === "never" || + !isAssignmentTarget(node.parent) || // ← ignore read-only references. + isGoodName(node.name) + ) { + return; + } + report(node); + }, - // ignore destructuring if the option is set, unless a new identifier is created - if (valueIsUnderscored && !(assignmentKeyEqualsValue && ignoreDestructuring)) { - report(node); - } + // Report camelcase in import -------------------------------------- + ImportDeclaration(node) { + for (const variable of context.getDeclaredVariables(node)) { + if (isGoodName(variable.name)) { + continue; } + const id = variable.identifiers[0]; - // "never" check properties or always ignore destructuring - if (properties === "never" || (ignoreDestructuring && isInsideObjectPattern(node))) { - return; + // Report declaration. + if (!(ignoreImports && equalsToOriginalName(id))) { + report(id); } - // don't check right hand side of AssignmentExpression to prevent duplicate warnings - if (nameIsUnderscored && !ALLOWED_PARENT_TYPES.has(effectiveParent.type) && !(node.parent.right === node)) { - report(node); + /* + * For backward compatibility, report references as well. + * It looks unnecessary because declarations are reported. + */ + for (const reference of variable.references) { + reportReferenceId(reference.identifier); } + } + }, - // Check if it's an import specifier - } else if (["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"].includes(node.parent.type)) { - - if (node.parent.type === "ImportSpecifier" && ignoreImports) { - return; - } + // Report camelcase in re-export ----------------------------------- + [[ + "ExportAllDeclaration > Identifier.exported", + "ExportSpecifier > Identifier.exported" + ]](node) { + if (isGoodName(node.name)) { + return; + } + report(node); + }, - // Report only if the local imported identifier is underscored - if ( - node.parent.local && - node.parent.local.name === node.name && - nameIsUnderscored - ) { - report(node); - } + // Report camelcase in labels -------------------------------------- + [[ + "LabeledStatement > Identifier.label", - // Report anything that is underscored that isn't a CallExpression - } else if (nameIsUnderscored && !ALLOWED_PARENT_TYPES.has(effectiveParent.type)) { - report(node); + /* + * For backward compatibility, report references as well. + * It looks unnecessary because declarations are reported. + */ + "BreakStatement > Identifier.label", + "ContinueStatement > Identifier.label" + ]](node) { + if (isGoodName(node.name)) { + return; } + report(node); } - }; - } }; diff --git a/eslint/lib/rules/capitalized-comments.js b/eslint/lib/rules/capitalized-comments.js index d7524b8..e5f4293 100644 --- a/eslint/lib/rules/capitalized-comments.js +++ b/eslint/lib/rules/capitalized-comments.js @@ -105,7 +105,6 @@ module.exports = { docs: { description: "enforce or disallow capitalization of the first letter of a comment", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/capitalized-comments" }, diff --git a/eslint/lib/rules/class-methods-use-this.js b/eslint/lib/rules/class-methods-use-this.js index 2cc5cc4..beb742d 100644 --- a/eslint/lib/rules/class-methods-use-this.js +++ b/eslint/lib/rules/class-methods-use-this.js @@ -21,7 +21,6 @@ module.exports = { docs: { description: "enforce that class methods utilize `this`", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/class-methods-use-this" }, @@ -34,6 +33,10 @@ module.exports = { items: { type: "string" } + }, + enforceForClassFields: { + type: "boolean", + default: true } }, additionalProperties: false @@ -45,10 +48,27 @@ module.exports = { }, create(context) { const config = Object.assign({}, context.options[0]); + const enforceForClassFields = config.enforceForClassFields !== false; const exceptMethods = new Set(config.exceptMethods || []); const stack = []; + /** + * Push `this` used flag initialized with `false` onto the stack. + * @returns {void} + */ + function pushContext() { + stack.push(false); + } + + /** + * Pop `this` used flag from the stack. + * @returns {boolean | undefined} `this` used flag + */ + function popContext() { + return stack.pop(); + } + /** * Initializes the current context to false and pushes it onto the stack. * These booleans represent whether 'this' has been used in the context. @@ -56,7 +76,7 @@ module.exports = { * @private */ function enterFunction() { - stack.push(false); + pushContext(); } /** @@ -66,7 +86,14 @@ module.exports = { * @private */ function isInstanceMethod(node) { - return !node.static && node.kind !== "constructor" && node.type === "MethodDefinition"; + switch (node.type) { + case "MethodDefinition": + return !node.static && node.kind !== "constructor"; + case "PropertyDefinition": + return !node.static && enforceForClassFields; + default: + return false; + } } /** @@ -76,8 +103,19 @@ module.exports = { * @private */ function isIncludedInstanceMethod(node) { - return isInstanceMethod(node) && - (node.computed || !exceptMethods.has(node.key.name)); + if (isInstanceMethod(node)) { + if (node.computed) { + return true; + } + + const hashIfNeeded = node.key.type === "PrivateIdentifier" ? "#" : ""; + const name = node.key.type === "Literal" + ? astUtils.getStaticStringValue(node.key) + : (node.key.name || ""); + + return !exceptMethods.has(hashIfNeeded + name); + } + return false; } /** @@ -89,11 +127,12 @@ module.exports = { * @private */ function exitFunction(node) { - const methodUsesThis = stack.pop(); + const methodUsesThis = popContext(); if (isIncludedInstanceMethod(node.parent) && !methodUsesThis) { context.report({ node, + loc: astUtils.getFunctionHeadLoc(node, context.getSourceCode()), messageId: "missingThis", data: { name: astUtils.getFunctionNameWithKind(node) @@ -118,8 +157,30 @@ module.exports = { "FunctionDeclaration:exit": exitFunction, FunctionExpression: enterFunction, "FunctionExpression:exit": exitFunction, + + /* + * Class field value are implicit functions. + */ + "PropertyDefinition > *.key:exit": pushContext, + "PropertyDefinition:exit": popContext, + + /* + * Class static blocks are implicit functions. They aren't required to use `this`, + * but we have to push context so that it captures any use of `this` in the static block + * separately from enclosing contexts, because static blocks have their own `this` and it + * shouldn't count as used `this` in enclosing contexts. + */ + StaticBlock: pushContext, + "StaticBlock:exit": popContext, + ThisExpression: markThisUsed, - Super: markThisUsed + Super: markThisUsed, + ...( + enforceForClassFields && { + "PropertyDefinition > ArrowFunctionExpression.value": enterFunction, + "PropertyDefinition > ArrowFunctionExpression.value:exit": exitFunction + } + ) }; } }; diff --git a/eslint/lib/rules/comma-dangle.js b/eslint/lib/rules/comma-dangle.js index 798c111..e97a598 100644 --- a/eslint/lib/rules/comma-dangle.js +++ b/eslint/lib/rules/comma-dangle.js @@ -76,7 +76,6 @@ module.exports = { docs: { description: "require or disallow trailing commas", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/comma-dangle" }, @@ -123,7 +122,8 @@ module.exports = { } ] } - ] + ], + additionalItems: false }, messages: { diff --git a/eslint/lib/rules/comma-spacing.js b/eslint/lib/rules/comma-spacing.js index 2bf41a0..d30a5ef 100644 --- a/eslint/lib/rules/comma-spacing.js +++ b/eslint/lib/rules/comma-spacing.js @@ -16,7 +16,6 @@ module.exports = { docs: { description: "enforce consistent spacing before and after commas", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/comma-spacing" }, diff --git a/eslint/lib/rules/comma-style.js b/eslint/lib/rules/comma-style.js index f1a23d6..1d62fcf 100644 --- a/eslint/lib/rules/comma-style.js +++ b/eslint/lib/rules/comma-style.js @@ -17,7 +17,6 @@ module.exports = { docs: { description: "enforce consistent comma style", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/comma-style" }, @@ -207,8 +206,7 @@ module.exports = { * they are always valid regardless of an undefined item. */ if (astUtils.isCommaToken(commaToken)) { - validateCommaItemSpacing(previousItemToken, commaToken, - currentItemToken, reportItem); + validateCommaItemSpacing(previousItemToken, commaToken, currentItemToken, reportItem); } if (item) { @@ -217,6 +215,8 @@ module.exports = { previousItemToken = tokenAfterItem ? sourceCode.getTokenBefore(tokenAfterItem) : sourceCode.ast.tokens[sourceCode.ast.tokens.length - 1]; + } else { + previousItemToken = currentItemToken; } }); diff --git a/eslint/lib/rules/complexity.js b/eslint/lib/rules/complexity.js index 116c8ad..a247039 100644 --- a/eslint/lib/rules/complexity.js +++ b/eslint/lib/rules/complexity.js @@ -23,7 +23,6 @@ module.exports = { docs: { description: "enforce a maximum cyclomatic complexity allowed in a program", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/complexity" }, @@ -75,60 +74,16 @@ module.exports = { // Helpers //-------------------------------------------------------------------------- - // Using a stack to store complexity (handling nested functions) - const fns = []; + // Using a stack to store complexity per code path + const complexities = []; /** - * When parsing a new function, store it in our function stack - * @returns {void} - * @private - */ - function startFunction() { - fns.push(1); - } - - /** - * Evaluate the node at the end of function - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function endFunction(node) { - const name = upperCaseFirst(astUtils.getFunctionNameWithKind(node)); - const complexity = fns.pop(); - - if (complexity > THRESHOLD) { - context.report({ - node, - messageId: "complex", - data: { name, complexity, max: THRESHOLD } - }); - } - } - - /** - * Increase the complexity of the function in context + * Increase the complexity of the code path in context * @returns {void} * @private */ function increaseComplexity() { - if (fns.length) { - fns[fns.length - 1]++; - } - } - - /** - * Increase the switch complexity in context - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function increaseSwitchComplexity(node) { - - // Avoiding `default` - if (node.test) { - increaseComplexity(); - } + complexities[complexities.length - 1]++; } //-------------------------------------------------------------------------- @@ -136,13 +91,14 @@ module.exports = { //-------------------------------------------------------------------------- return { - FunctionDeclaration: startFunction, - FunctionExpression: startFunction, - ArrowFunctionExpression: startFunction, - "FunctionDeclaration:exit": endFunction, - "FunctionExpression:exit": endFunction, - "ArrowFunctionExpression:exit": endFunction, + onCodePathStart() { + + // The initial complexity is 1, representing one execution path in the CodePath + complexities.push(1); + }, + + // Each branching in the code adds 1 to the complexity CatchClause: increaseComplexity, ConditionalExpression: increaseComplexity, LogicalExpression: increaseComplexity, @@ -150,14 +106,57 @@ module.exports = { ForInStatement: increaseComplexity, ForOfStatement: increaseComplexity, IfStatement: increaseComplexity, - SwitchCase: increaseSwitchComplexity, WhileStatement: increaseComplexity, DoWhileStatement: increaseComplexity, + // Avoid `default` + "SwitchCase[test]": increaseComplexity, + + // Logical assignment operators have short-circuiting behavior AssignmentExpression(node) { if (astUtils.isLogicalAssignmentOperator(node.operator)) { increaseComplexity(); } + }, + + onCodePathEnd(codePath, node) { + const complexity = complexities.pop(); + + /* + * This rule only evaluates complexity of functions, so "program" is excluded. + * Class field initializers and class static blocks are implicit functions. Therefore, + * they shouldn't contribute to the enclosing function's complexity, but their + * own complexity should be evaluated. + */ + if ( + codePath.origin !== "function" && + codePath.origin !== "class-field-initializer" && + codePath.origin !== "class-static-block" + ) { + return; + } + + if (complexity > THRESHOLD) { + let name; + + if (codePath.origin === "class-field-initializer") { + name = "class field initializer"; + } else if (codePath.origin === "class-static-block") { + name = "class static block"; + } else { + name = astUtils.getFunctionNameWithKind(node); + } + + context.report({ + node, + messageId: "complex", + data: { + name: upperCaseFirst(name), + complexity, + max: THRESHOLD + } + }); + } } }; diff --git a/eslint/lib/rules/computed-property-spacing.js b/eslint/lib/rules/computed-property-spacing.js index 53fdb8f..c8d8834 100644 --- a/eslint/lib/rules/computed-property-spacing.js +++ b/eslint/lib/rules/computed-property-spacing.js @@ -16,7 +16,6 @@ module.exports = { docs: { description: "enforce consistent spacing inside computed property brackets", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/computed-property-spacing" }, @@ -195,7 +194,8 @@ module.exports = { }; if (enforceForClassMembers) { - listeners.MethodDefinition = checkSpacing("key"); + listeners.MethodDefinition = + listeners.PropertyDefinition = listeners.Property; } return listeners; diff --git a/eslint/lib/rules/consistent-return.js b/eslint/lib/rules/consistent-return.js index a250430..b509c36 100644 --- a/eslint/lib/rules/consistent-return.js +++ b/eslint/lib/rules/consistent-return.js @@ -46,7 +46,6 @@ module.exports = { docs: { description: "require `return` statements to either always or never specify values", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/consistent-return" }, @@ -104,18 +103,18 @@ module.exports = { } else if (node.type === "ArrowFunctionExpression") { // `=>` token - loc = context.getSourceCode().getTokenBefore(node.body, astUtils.isArrowToken).loc.start; + loc = context.getSourceCode().getTokenBefore(node.body, astUtils.isArrowToken).loc; } else if ( node.parent.type === "MethodDefinition" || (node.parent.type === "Property" && node.parent.method) ) { // Method name. - loc = node.parent.key.loc.start; + loc = node.parent.key.loc; } else { // Function name or `function` keyword. - loc = (node.id || node).loc.start; + loc = (node.id || context.getSourceCode().getFirstToken(node)).loc; } if (!name) { diff --git a/eslint/lib/rules/consistent-this.js b/eslint/lib/rules/consistent-this.js index e5bc967..025f3d0 100644 --- a/eslint/lib/rules/consistent-this.js +++ b/eslint/lib/rules/consistent-this.js @@ -14,7 +14,6 @@ module.exports = { docs: { description: "enforce consistent naming when capturing the current execution context", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/consistent-this" }, @@ -47,7 +46,7 @@ module.exports = { * Reports that a variable declarator or assignment expression is assigning * a non-'this' value to the specified alias. * @param {ASTNode} node The assigning node. - * @param {string} name the name of the alias that was incorrectly used. + * @param {string} name the name of the alias that was incorrectly used. * @returns {void} */ function reportBadAssignment(node, name) { diff --git a/eslint/lib/rules/constructor-super.js b/eslint/lib/rules/constructor-super.js index dfec18f..38eb489 100644 --- a/eslint/lib/rules/constructor-super.js +++ b/eslint/lib/rules/constructor-super.js @@ -122,7 +122,6 @@ module.exports = { docs: { description: "require `super()` calls in constructors", - category: "ECMAScript 6", recommended: true, url: "https://eslint.org/docs/rules/constructor-super" }, diff --git a/eslint/lib/rules/curly.js b/eslint/lib/rules/curly.js index 92d31a6..57c2e72 100644 --- a/eslint/lib/rules/curly.js +++ b/eslint/lib/rules/curly.js @@ -20,7 +20,6 @@ module.exports = { docs: { description: "enforce consistent brace style for all control statements", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/curly" }, @@ -131,15 +130,6 @@ module.exports = { return token.value === "else" && token.type === "Keyword"; } - /** - * Gets the `else` keyword token of a given `IfStatement` node. - * @param {ASTNode} node A `IfStatement` node to get. - * @returns {Token} The `else` keyword token. - */ - function getElseKeyword(node) { - return node.alternate && sourceCode.getFirstTokenBetween(node.consequent, node.alternate, isElseKeywordToken); - } - /** * Determines whether the given node has an `else` keyword token as the first token after. * @param {ASTNode} node The node to check. @@ -361,7 +351,7 @@ module.exports = { if (this.expected) { context.report({ node, - loc: (name !== "else" ? node : getElseKeyword(node)).loc.start, + loc: body.loc, messageId: opts && opts.condition ? "missingCurlyAfterCondition" : "missingCurlyAfter", data: { name @@ -371,7 +361,7 @@ module.exports = { } else { context.report({ node, - loc: (name !== "else" ? node : getElseKeyword(node)).loc.start, + loc: body.loc, messageId: opts && opts.condition ? "unexpectedCurlyAfterCondition" : "unexpectedCurlyAfter", data: { name diff --git a/eslint/lib/rules/default-case-last.js b/eslint/lib/rules/default-case-last.js index 80c5d6b..1eeadd1 100644 --- a/eslint/lib/rules/default-case-last.js +++ b/eslint/lib/rules/default-case-last.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "enforce default clauses in switch statements to be last", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/default-case-last" }, diff --git a/eslint/lib/rules/default-case.js b/eslint/lib/rules/default-case.js index 821e0d7..b839aa2 100644 --- a/eslint/lib/rules/default-case.js +++ b/eslint/lib/rules/default-case.js @@ -16,7 +16,6 @@ module.exports = { docs: { description: "require `default` cases in `switch` statements", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/default-case" }, @@ -50,8 +49,8 @@ module.exports = { /** * Shortcut to get last element of array - * @param {*[]} collection Array - * @returns {*} Last element + * @param {*[]} collection Array + * @returns {any} Last element */ function last(collection) { return collection[collection.length - 1]; diff --git a/eslint/lib/rules/default-param-last.js b/eslint/lib/rules/default-param-last.js index 12e0b59..8382d46 100644 --- a/eslint/lib/rules/default-param-last.js +++ b/eslint/lib/rules/default-param-last.js @@ -11,7 +11,6 @@ module.exports = { docs: { description: "enforce default parameters to be last", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/default-param-last" }, @@ -25,8 +24,8 @@ module.exports = { create(context) { - // eslint-disable-next-line jsdoc/require-description /** + * Handler for function contexts. * @param {ASTNode} node function node * @returns {void} */ diff --git a/eslint/lib/rules/dot-location.js b/eslint/lib/rules/dot-location.js index a8d5a76..d80f870 100644 --- a/eslint/lib/rules/dot-location.js +++ b/eslint/lib/rules/dot-location.js @@ -17,7 +17,6 @@ module.exports = { docs: { description: "enforce consistent newlines before and after dots", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/dot-location" }, diff --git a/eslint/lib/rules/dot-notation.js b/eslint/lib/rules/dot-notation.js index 751b462..1cd908f 100644 --- a/eslint/lib/rules/dot-notation.js +++ b/eslint/lib/rules/dot-notation.js @@ -26,7 +26,6 @@ module.exports = { docs: { description: "enforce dot notation whenever possible", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/dot-notation" }, @@ -94,7 +93,7 @@ module.exports = { // Don't perform any fixes if there are comments inside the brackets. if (sourceCode.commentsExistBetween(leftBracket, rightBracket)) { - return; // eslint-disable-line eslint-plugin/fixer-return -- false positive + return; } // Replace the brackets by an identifier. @@ -141,6 +140,7 @@ module.exports = { if ( !allowKeywords && !node.computed && + node.property.type === "Identifier" && keywords.indexOf(String(node.property.name)) !== -1 ) { context.report({ @@ -154,12 +154,12 @@ module.exports = { // A statement that starts with `let[` is parsed as a destructuring variable declaration, not a MemberExpression. if (node.object.type === "Identifier" && node.object.name === "let" && !node.optional) { - return; // eslint-disable-line eslint-plugin/fixer-return -- false positive + return; } // Don't perform any fixes if there are comments between the dot and the property name. if (sourceCode.commentsExistBetween(dotToken, node.property)) { - return; // eslint-disable-line eslint-plugin/fixer-return -- false positive + return; } // Replace the identifier to brackets. diff --git a/eslint/lib/rules/eol-last.js b/eslint/lib/rules/eol-last.js index 24b0c92..f8b922c 100644 --- a/eslint/lib/rules/eol-last.js +++ b/eslint/lib/rules/eol-last.js @@ -14,7 +14,6 @@ module.exports = { docs: { description: "require or disallow newline at the end of files", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/eol-last" }, @@ -86,10 +85,15 @@ module.exports = { }); } else if (mode === "never" && endsWithNewline) { + const secondLastLine = sourceCode.lines[sourceCode.lines.length - 2]; + // File is newline-terminated, but shouldn't be context.report({ node, - loc: location, + loc: { + start: { line: sourceCode.lines.length - 1, column: secondLastLine.length }, + end: { line: sourceCode.lines.length, column: 0 } + }, messageId: "unexpected", fix(fixer) { const finalEOLs = /(?:\r?\n)+$/u, diff --git a/eslint/lib/rules/eqeqeq.js b/eslint/lib/rules/eqeqeq.js index 57926db..d3e6b5a 100644 --- a/eslint/lib/rules/eqeqeq.js +++ b/eslint/lib/rules/eqeqeq.js @@ -21,7 +21,6 @@ module.exports = { docs: { description: "require the use of `===` and `!==`", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/eqeqeq" }, @@ -78,7 +77,7 @@ module.exports = { /** * Checks if an expression is a typeof expression - * @param {ASTNode} node The node to check + * @param {ASTNode} node The node to check * @returns {boolean} if the node is a typeof expression */ function isTypeOf(node) { diff --git a/eslint/lib/rules/for-direction.js b/eslint/lib/rules/for-direction.js index c15d10e..abe4ad3 100644 --- a/eslint/lib/rules/for-direction.js +++ b/eslint/lib/rules/for-direction.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "enforce \"for\" loop update clause moving the counter in the right direction.", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/for-direction" }, diff --git a/eslint/lib/rules/func-call-spacing.js b/eslint/lib/rules/func-call-spacing.js index 132a583..a6ebde4 100644 --- a/eslint/lib/rules/func-call-spacing.js +++ b/eslint/lib/rules/func-call-spacing.js @@ -21,7 +21,6 @@ module.exports = { docs: { description: "require or disallow spacing between function identifiers and their invocations", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/func-call-spacing" }, diff --git a/eslint/lib/rules/func-name-matching.js b/eslint/lib/rules/func-name-matching.js index 755c2ee..122cfd8 100644 --- a/eslint/lib/rules/func-name-matching.js +++ b/eslint/lib/rules/func-name-matching.js @@ -74,7 +74,6 @@ module.exports = { docs: { description: "require function names to match the name of the variable or property to which they are assigned", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/func-name-matching" }, @@ -196,21 +195,25 @@ module.exports = { const isProp = node.left.type === "MemberExpression"; const name = isProp ? astUtils.getStaticPropertyName(node.left) : node.left.name; - if (node.right.id && isIdentifier(name) && shouldWarn(name, node.right.id.name)) { + if (node.right.id && name && isIdentifier(name) && shouldWarn(name, node.right.id.name)) { report(node, name, node.right.id.name, isProp); } }, - Property(node) { - if (node.value.type !== "FunctionExpression" || !node.value.id || node.computed && !isStringLiteral(node.key)) { + "Property, PropertyDefinition[value]"(node) { + if (!(node.value.type === "FunctionExpression" && node.value.id)) { return; } - if (node.key.type === "Identifier") { + if (node.key.type === "Identifier" && !node.computed) { const functionName = node.value.id.name; let propertyName = node.key.name; - if (considerPropertyDescriptor && propertyName === "value") { + if ( + considerPropertyDescriptor && + propertyName === "value" && + node.parent.type === "ObjectExpression" + ) { if (isPropertyCall("Object", "defineProperty", node.parent.parent) || isPropertyCall("Reflect", "defineProperty", node.parent.parent)) { const property = node.parent.parent.arguments[1]; diff --git a/eslint/lib/rules/func-names.js b/eslint/lib/rules/func-names.js index ecfedb9..589903c 100644 --- a/eslint/lib/rules/func-names.js +++ b/eslint/lib/rules/func-names.js @@ -30,7 +30,6 @@ module.exports = { docs: { description: "require or disallow named `function` expressions", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/func-names" }, @@ -118,6 +117,7 @@ module.exports = { return isObjectOrClassMethod(node) || (parent.type === "VariableDeclarator" && parent.id.type === "Identifier" && parent.init === node) || (parent.type === "Property" && parent.value === node) || + (parent.type === "PropertyDefinition" && parent.value === node) || (parent.type === "AssignmentExpression" && parent.left.type === "Identifier" && parent.right === node) || (parent.type === "AssignmentPattern" && parent.left.type === "Identifier" && parent.right === node); } diff --git a/eslint/lib/rules/func-style.js b/eslint/lib/rules/func-style.js index e150b1a..0921ff5 100644 --- a/eslint/lib/rules/func-style.js +++ b/eslint/lib/rules/func-style.js @@ -14,7 +14,6 @@ module.exports = { docs: { description: "enforce the consistent use of either `function` declarations or expressions", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/func-style" }, diff --git a/eslint/lib/rules/function-call-argument-newline.js b/eslint/lib/rules/function-call-argument-newline.js index b6abbe9..ed4e296 100644 --- a/eslint/lib/rules/function-call-argument-newline.js +++ b/eslint/lib/rules/function-call-argument-newline.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "enforce line breaks between arguments of a function call", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/function-call-argument-newline" }, diff --git a/eslint/lib/rules/function-paren-newline.js b/eslint/lib/rules/function-paren-newline.js index 9d8d67b..18435b7 100644 --- a/eslint/lib/rules/function-paren-newline.js +++ b/eslint/lib/rules/function-paren-newline.js @@ -20,7 +20,6 @@ module.exports = { docs: { description: "enforce consistent line breaks inside function parentheses", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/function-paren-newline" }, @@ -183,6 +182,7 @@ module.exports = { /** * Gets the left paren and right paren tokens of a node. * @param {ASTNode} node The node with parens + * @throws {TypeError} Unexecpted node type. * @returns {Object} An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token. * Can also return `null` if an expression has no parens (e.g. a NewExpression with no arguments, or an ArrowFunctionExpression * with a single parameter) diff --git a/eslint/lib/rules/generator-star-spacing.js b/eslint/lib/rules/generator-star-spacing.js index 65534f7..c50445c 100644 --- a/eslint/lib/rules/generator-star-spacing.js +++ b/eslint/lib/rules/generator-star-spacing.js @@ -31,7 +31,6 @@ module.exports = { docs: { description: "enforce consistent spacing around `*` operators in generator functions", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/generator-star-spacing" }, diff --git a/eslint/lib/rules/getter-return.js b/eslint/lib/rules/getter-return.js index c54ebfb..8bb4253 100644 --- a/eslint/lib/rules/getter-return.js +++ b/eslint/lib/rules/getter-return.js @@ -35,7 +35,6 @@ module.exports = { docs: { description: "enforce `return` statements in getters", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/getter-return" }, diff --git a/eslint/lib/rules/global-require.js b/eslint/lib/rules/global-require.js index 09d0332..f2d29d1 100644 --- a/eslint/lib/rules/global-require.js +++ b/eslint/lib/rules/global-require.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule for disallowing require() outside of the top-level module context * @author Jamund Ferguson + * @deprecated in ESLint v7.0.0 */ "use strict"; @@ -57,7 +58,6 @@ module.exports = { docs: { description: "require `require()` calls to be placed at top-level module scope", - category: "Node.js and CommonJS", recommended: false, url: "https://eslint.org/docs/rules/global-require" }, diff --git a/eslint/lib/rules/grouped-accessor-pairs.js b/eslint/lib/rules/grouped-accessor-pairs.js index a790f83..cc4a4b5 100644 --- a/eslint/lib/rules/grouped-accessor-pairs.js +++ b/eslint/lib/rules/grouped-accessor-pairs.js @@ -96,7 +96,6 @@ module.exports = { docs: { description: "require grouped accessor pairs in object literals and classes", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/grouped-accessor-pairs" }, diff --git a/eslint/lib/rules/guard-for-in.js b/eslint/lib/rules/guard-for-in.js index 2c0976d..6f877ba 100644 --- a/eslint/lib/rules/guard-for-in.js +++ b/eslint/lib/rules/guard-for-in.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "require `for-in` loops to include an `if` statement", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/guard-for-in" }, diff --git a/eslint/lib/rules/handle-callback-err.js b/eslint/lib/rules/handle-callback-err.js index b92490a..cdb3a76 100644 --- a/eslint/lib/rules/handle-callback-err.js +++ b/eslint/lib/rules/handle-callback-err.js @@ -1,6 +1,7 @@ /** * @fileoverview Ensure handling of errors when we know they exist. * @author Jamund Ferguson + * @deprecated in ESLint v7.0.0 */ "use strict"; @@ -19,7 +20,6 @@ module.exports = { docs: { description: "require error handling in callbacks", - category: "Node.js and CommonJS", recommended: false, url: "https://eslint.org/docs/rules/handle-callback-err" }, diff --git a/eslint/lib/rules/id-blacklist.js b/eslint/lib/rules/id-blacklist.js index 4fbba90..77deac7 100644 --- a/eslint/lib/rules/id-blacklist.js +++ b/eslint/lib/rules/id-blacklist.js @@ -2,6 +2,7 @@ * @fileoverview Rule that warns when identifier names that are * specified in the configuration are used. * @author Keith Cirkel (http://keithcirkel.co.uk) + * @deprecated in ESLint v7.5.0 */ "use strict"; @@ -118,7 +119,6 @@ module.exports = { docs: { description: "disallow specified identifiers", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/id-blacklist" }, @@ -205,7 +205,17 @@ module.exports = { * @private */ function report(node) { - if (!reportedNodes.has(node)) { + + /* + * We used the range instead of the node because it's possible + * for the same identifier to be represented by two different + * nodes, with the most clear example being shorthand properties: + * { foo } + * In this case, "foo" is represented by one node for the name + * and one for the value. The only way to know they are the same + * is to look at the range. + */ + if (!reportedNodes.has(node.range.toString())) { context.report({ node, messageId: "restricted", @@ -213,8 +223,9 @@ module.exports = { name: node.name } }); - reportedNodes.add(node); + reportedNodes.add(node.range.toString()); } + } return { diff --git a/eslint/lib/rules/id-denylist.js b/eslint/lib/rules/id-denylist.js index 112fd8a..2b34635 100644 --- a/eslint/lib/rules/id-denylist.js +++ b/eslint/lib/rules/id-denylist.js @@ -69,14 +69,14 @@ function isRenamedImport(node) { } /** - * Checks whether the given node is a renamed identifier node in an ObjectPattern destructuring. + * Checks whether the given node is an ObjectPattern destructuring. * * Examples: - * const { a : b } = foo; // node `a` is renamed node. + * const { a : b } = foo; * @param {ASTNode} node `Identifier` node to check. - * @returns {boolean} `true` if the node is a renamed node in an ObjectPattern destructuring. + * @returns {boolean} `true` if the node is in an ObjectPattern destructuring. */ -function isRenamedInDestructuring(node) { +function isPropertyNameInDestructuring(node) { const parent = node.parent; return ( @@ -84,27 +84,11 @@ function isRenamedInDestructuring(node) { !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 //------------------------------------------------------------------------------ @@ -115,7 +99,6 @@ module.exports = { docs: { description: "disallow specified identifiers", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/id-denylist" }, @@ -128,7 +111,8 @@ module.exports = { uniqueItems: true }, messages: { - restricted: "Identifier '{{name}}' is restricted." + restricted: "Identifier '{{name}}' is restricted.", + restrictedPrivate: "Identifier '#{{name}}' is restricted." } }, @@ -187,11 +171,8 @@ module.exports = { parent.type !== "CallExpression" && parent.type !== "NewExpression" && !isRenamedImport(node) && - !isRenamedInDestructuring(node) && - !( - isReferenceToGlobalVariable(node) && - !isShorthandPropertyDefinition(node) - ) + !isPropertyNameInDestructuring(node) && + !isReferenceToGlobalVariable(node) ); } @@ -202,15 +183,27 @@ module.exports = { * @private */ function report(node) { - if (!reportedNodes.has(node)) { + + /* + * We used the range instead of the node because it's possible + * for the same identifier to be represented by two different + * nodes, with the most clear example being shorthand properties: + * { foo } + * In this case, "foo" is represented by one node for the name + * and one for the value. The only way to know they are the same + * is to look at the range. + */ + if (!reportedNodes.has(node.range.toString())) { + const isPrivate = node.type === "PrivateIdentifier"; + context.report({ node, - messageId: "restricted", + messageId: isPrivate ? "restrictedPrivate" : "restricted", data: { name: node.name } }); - reportedNodes.add(node); + reportedNodes.add(node.range.toString()); } } @@ -220,7 +213,10 @@ module.exports = { globalScope = context.getScope(); }, - Identifier(node) { + [[ + "Identifier", + "PrivateIdentifier" + ]](node) { if (isRestricted(node.name) && shouldCheck(node)) { report(node); } diff --git a/eslint/lib/rules/id-length.js b/eslint/lib/rules/id-length.js index 4df081f..ac6385f 100644 --- a/eslint/lib/rules/id-length.js +++ b/eslint/lib/rules/id-length.js @@ -16,7 +16,6 @@ module.exports = { docs: { description: "enforce minimum and maximum identifier lengths", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/id-length" }, @@ -55,7 +54,9 @@ module.exports = { ], messages: { tooShort: "Identifier name '{{name}}' is too short (< {{min}}).", - tooLong: "Identifier name '{{name}}' is too long (> {{max}})." + tooShortPrivate: "Identifier name '#{{name}}' is too short (< {{min}}).", + tooLong: "Identifier name '{{name}}' is too long (> {{max}}).", + tooLongPrivate: "Identifier name #'{{name}}' is too long (> {{max}})." } }, @@ -66,7 +67,7 @@ module.exports = { const properties = options.properties !== "never"; const exceptions = new Set(options.exceptions); const exceptionPatterns = (options.exceptionPatterns || []).map(pattern => new RegExp(pattern, "u")); - const reportedNode = new Set(); + const reportedNodes = new Set(); /** * Checks if a string matches the provided exception patterns @@ -99,12 +100,14 @@ module.exports = { Property(parent, node) { if (parent.parent.type === "ObjectPattern") { + const isKeyAndValueSame = parent.value.name === parent.key.name; + return ( - parent.value !== parent.key && parent.value === node || - parent.value === parent.key && parent.key === node && properties + !isKeyAndValueSame && parent.value === node || + isKeyAndValueSame && parent.key === node && properties ); } - return properties && !parent.computed && parent.key === node; + return properties && !parent.computed && parent.key.name === node.name; }, ImportDefaultSpecifier: true, RestElement: true, @@ -113,12 +116,16 @@ module.exports = { ClassDeclaration: true, FunctionDeclaration: true, MethodDefinition: true, + PropertyDefinition: true, CatchClause: true, ArrayPattern: true }; return { - Identifier(node) { + [[ + "Identifier", + "PrivateIdentifier" + ]](node) { const name = node.name; const parent = node.parent; @@ -131,11 +138,27 @@ module.exports = { const isValidExpression = SUPPORTED_EXPRESSIONS[parent.type]; - if (isValidExpression && !reportedNode.has(node) && (isValidExpression === true || isValidExpression(parent, node))) { - reportedNode.add(node); + /* + * We used the range instead of the node because it's possible + * for the same identifier to be represented by two different + * nodes, with the most clear example being shorthand properties: + * { foo } + * In this case, "foo" is represented by one node for the name + * and one for the value. The only way to know they are the same + * is to look at the range. + */ + if (isValidExpression && !reportedNodes.has(node.range.toString()) && (isValidExpression === true || isValidExpression(parent, node))) { + reportedNodes.add(node.range.toString()); + + let messageId = isShort ? "tooShort" : "tooLong"; + + if (node.type === "PrivateIdentifier") { + messageId += "Private"; + } + context.report({ node, - messageId: isShort ? "tooShort" : "tooLong", + messageId, data: { name, min: minLength, max: maxLength } }); } diff --git a/eslint/lib/rules/id-match.js b/eslint/lib/rules/id-match.js index 7e400d0..7a6cd05 100644 --- a/eslint/lib/rules/id-match.js +++ b/eslint/lib/rules/id-match.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "require identifiers to match a specified regular expression", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/id-match" }, @@ -31,6 +30,10 @@ module.exports = { type: "boolean", default: false }, + classFields: { + type: "boolean", + default: false + }, onlyDeclarations: { type: "boolean", default: false @@ -44,7 +47,8 @@ module.exports = { } ], messages: { - notMatch: "Identifier '{{name}}' does not match the pattern '{{pattern}}'." + notMatch: "Identifier '{{name}}' does not match the pattern '{{pattern}}'.", + notMatchPrivate: "Identifier '#{{name}}' does not match the pattern '{{pattern}}'." } }, @@ -57,7 +61,8 @@ module.exports = { regexp = new RegExp(pattern, "u"); const options = context.options[1] || {}, - properties = !!options.properties, + checkProperties = !!options.properties, + checkClassFields = !!options.classFields, onlyDeclarations = !!options.onlyDeclarations, ignoreDestructuring = !!options.ignoreDestructuring; @@ -66,7 +71,7 @@ module.exports = { //-------------------------------------------------------------------------- // contains reported nodes to avoid reporting twice on destructuring with shorthand notation - const reported = new Map(); + const reportedNodes = new Set(); const ALLOWED_PARENT_TYPES = new Set(["CallExpression", "NewExpression"]); const DECLARATION_TYPES = new Set(["FunctionDeclaration", "VariableDeclarator"]); const IMPORT_TYPES = new Set(["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"]); @@ -120,16 +125,30 @@ module.exports = { * @private */ function report(node) { - if (!reported.has(node)) { + + /* + * We used the range instead of the node because it's possible + * for the same identifier to be represented by two different + * nodes, with the most clear example being shorthand properties: + * { foo } + * In this case, "foo" is represented by one node for the name + * and one for the value. The only way to know they are the same + * is to look at the range. + */ + if (!reportedNodes.has(node.range.toString())) { + + const messageId = (node.type === "PrivateIdentifier") + ? "notMatchPrivate" : "notMatch"; + context.report({ node, - messageId: "notMatch", + messageId, data: { name: node.name, pattern } }); - reported.set(node, true); + reportedNodes.add(node.range.toString()); } } @@ -142,7 +161,7 @@ module.exports = { if (parent.type === "MemberExpression") { - if (!properties) { + if (!checkProperties) { return; } @@ -176,8 +195,7 @@ module.exports = { } else if (parent.type === "Property" || parent.type === "AssignmentPattern") { if (parent.parent && parent.parent.type === "ObjectPattern") { - if (parent.shorthand && parent.value.left && isInvalid(name)) { - + if (!ignoreDestructuring && parent.shorthand && parent.value.left && isInvalid(name)) { report(node); } @@ -197,7 +215,7 @@ module.exports = { } // never check properties or always ignore destructuring - if (!properties || (ignoreDestructuring && isInsideObjectPattern(node))) { + if (!checkProperties || (ignoreDestructuring && isInsideObjectPattern(node))) { return; } @@ -214,10 +232,29 @@ module.exports = { report(node); } + } else if (parent.type === "PropertyDefinition") { + + if (checkClassFields && isInvalid(name)) { + report(node); + } + // Report anything that is invalid that isn't a CallExpression } else if (shouldReport(effectiveParent, name)) { report(node); } + }, + + "PrivateIdentifier"(node) { + + const isClassField = node.parent.type === "PropertyDefinition"; + + if (isClassField && !checkClassFields) { + return; + } + + if (isInvalid(node.name)) { + report(node); + } } }; diff --git a/eslint/lib/rules/implicit-arrow-linebreak.js b/eslint/lib/rules/implicit-arrow-linebreak.js index 409145e..2d09552 100644 --- a/eslint/lib/rules/implicit-arrow-linebreak.js +++ b/eslint/lib/rules/implicit-arrow-linebreak.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "enforce the location of arrow function bodies", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/implicit-arrow-linebreak" }, diff --git a/eslint/lib/rules/indent-legacy.js b/eslint/lib/rules/indent-legacy.js index a26ee87..54ca9dd 100644 --- a/eslint/lib/rules/indent-legacy.js +++ b/eslint/lib/rules/indent-legacy.js @@ -4,6 +4,7 @@ * This rule has been ported and modified from nodeca. * @author Vitaly Puzrin * @author Gyandeep Singh + * @deprecated in ESLint v4.0.0 */ "use strict"; @@ -25,7 +26,6 @@ module.exports = { docs: { description: "enforce consistent indentation", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/indent-legacy" }, diff --git a/eslint/lib/rules/indent.js b/eslint/lib/rules/indent.js index b1af2a7..f4dbfff 100644 --- a/eslint/lib/rules/indent.js +++ b/eslint/lib/rules/indent.js @@ -60,12 +60,15 @@ const KNOWN_NODES = new Set([ "NewExpression", "ObjectExpression", "ObjectPattern", + "PrivateIdentifier", "Program", "Property", + "PropertyDefinition", "RestElement", "ReturnStatement", "SequenceExpression", "SpreadElement", + "StaticBlock", "Super", "SwitchCase", "SwitchStatement", @@ -138,7 +141,7 @@ class BinarySearchTree { /** * Inserts an entry into the tree. * @param {number} key The entry's key - * @param {*} value The entry's value + * @param {any} value The entry's value * @returns {void} */ insert(key, value) { @@ -188,7 +191,6 @@ class BinarySearchTree { */ class TokenInfo { - // eslint-disable-next-line jsdoc/require-description /** * @param {SourceCode} sourceCode A SourceCode object */ @@ -238,7 +240,6 @@ class TokenInfo { */ class OffsetStorage { - // eslint-disable-next-line jsdoc/require-description /** * @param {TokenInfo} tokenInfo a TokenInfo instance * @param {number} indentSize The desired size of each indentation level @@ -263,7 +264,7 @@ class OffsetStorage { /** * Sets the offset column of token B to match the offset column of token A. - * **WARNING**: This matches a *column*, even if baseToken is not the first token on its line. In + * - **WARNING**: This matches a *column*, even if baseToken is not the first token on its line. In * most cases, `setDesiredOffset` should be used instead. * @param {Token} baseToken The first token * @param {Token} offsetToken The second token, whose offset should be matched to the first token @@ -352,11 +353,11 @@ class OffsetStorage { * Instead, the offset tree is represented as a collection of contiguous offset ranges in a file. For example, the following * list could represent the state of the offset tree at a given point: * - * * Tokens starting in the interval [0, 15) are aligned with the beginning of the file - * * Tokens starting in the interval [15, 30) are offset by 1 indent level from the `bar` token - * * Tokens starting in the interval [30, 43) are offset by 1 indent level from the `foo` token - * * Tokens starting in the interval [43, 820) are offset by 2 indent levels from the `bar` token - * * Tokens starting in the interval [820, ∞) are offset by 1 indent level from the `baz` token + * - Tokens starting in the interval [0, 15) are aligned with the beginning of the file + * - Tokens starting in the interval [15, 30) are offset by 1 indent level from the `bar` token + * - Tokens starting in the interval [30, 43) are offset by 1 indent level from the `foo` token + * - Tokens starting in the interval [43, 820) are offset by 2 indent levels from the `bar` token + * - Tokens starting in the interval [820, ∞) are offset by 1 indent level from the `baz` token * * The `setDesiredOffsets` methods inserts ranges like the ones above. The third line above would be inserted by using: * `setDesiredOffsets([30, 43], fooToken, 1);` @@ -499,7 +500,6 @@ module.exports = { docs: { description: "enforce consistent indentation", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/indent" }, @@ -584,6 +584,16 @@ module.exports = { }, additionalProperties: false }, + StaticBlock: { + type: "object", + properties: { + body: { + type: "integer", + minimum: 0 + } + }, + additionalProperties: false + }, CallExpression: { type: "object", properties: { @@ -647,6 +657,9 @@ module.exports = { parameters: DEFAULT_PARAMETER_INDENT, body: DEFAULT_FUNCTION_BODY_INDENT }, + StaticBlock: { + body: DEFAULT_FUNCTION_BODY_INDENT + }, CallExpression: { arguments: DEFAULT_PARAMETER_INDENT }, @@ -1177,8 +1190,7 @@ module.exports = { offsets.setDesiredOffset(questionMarkToken, firstToken, 1); offsets.setDesiredOffset(colonToken, firstToken, 1); - offsets.setDesiredOffset(firstConsequentToken, firstToken, - firstConsequentToken.type === "Punctuator" && + offsets.setDesiredOffset(firstConsequentToken, firstToken, firstConsequentToken.type === "Punctuator" && options.offsetTernaryExpressions ? 2 : 1); /* @@ -1204,8 +1216,7 @@ module.exports = { * If `baz` were aligned with `bar` rather than being offset by 1 from `foo`, `baz` would end up * having no expected indentation. */ - offsets.setDesiredOffset(firstAlternateToken, firstToken, - firstAlternateToken.type === "Punctuator" && + offsets.setDesiredOffset(firstAlternateToken, firstToken, firstAlternateToken.type === "Punctuator" && options.offsetTernaryExpressions ? 2 : 1); } } @@ -1361,6 +1372,52 @@ module.exports = { } }, + PropertyDefinition(node) { + const firstToken = sourceCode.getFirstToken(node); + const maybeSemicolonToken = sourceCode.getLastToken(node); + let keyLastToken = null; + + // Indent key. + if (node.computed) { + const bracketTokenL = sourceCode.getTokenBefore(node.key, astUtils.isOpeningBracketToken); + const bracketTokenR = keyLastToken = sourceCode.getTokenAfter(node.key, astUtils.isClosingBracketToken); + const keyRange = [bracketTokenL.range[1], bracketTokenR.range[0]]; + + if (bracketTokenL !== firstToken) { + offsets.setDesiredOffset(bracketTokenL, firstToken, 0); + } + offsets.setDesiredOffsets(keyRange, bracketTokenL, 1); + offsets.setDesiredOffset(bracketTokenR, bracketTokenL, 0); + } else { + const idToken = keyLastToken = sourceCode.getFirstToken(node.key); + + if (idToken !== firstToken) { + offsets.setDesiredOffset(idToken, firstToken, 1); + } + } + + // Indent initializer. + if (node.value) { + const eqToken = sourceCode.getTokenBefore(node.value, astUtils.isEqToken); + const valueToken = sourceCode.getTokenAfter(eqToken); + + offsets.setDesiredOffset(eqToken, keyLastToken, 1); + offsets.setDesiredOffset(valueToken, eqToken, 1); + if (astUtils.isSemicolonToken(maybeSemicolonToken)) { + offsets.setDesiredOffset(maybeSemicolonToken, eqToken, 1); + } + } else if (astUtils.isSemicolonToken(maybeSemicolonToken)) { + offsets.setDesiredOffset(maybeSemicolonToken, keyLastToken, 1); + } + }, + + StaticBlock(node) { + const openingCurly = sourceCode.getFirstToken(node, { skip: 1 }); // skip the `static` token + const closingCurly = sourceCode.getLastToken(node); + + addElementListIndent(node.body, openingCurly, closingCurly, options.StaticBlock.body); + }, + SwitchStatement(node) { const openingCurly = sourceCode.getTokenAfter(node.discriminant, astUtils.isOpeningBraceToken); const closingCurly = sourceCode.getLastToken(node); diff --git a/eslint/lib/rules/index.js b/eslint/lib/rules/index.js index 35af38f..ed322a4 100644 --- a/eslint/lib/rules/index.js +++ b/eslint/lib/rules/index.js @@ -6,7 +6,7 @@ "use strict"; -/* eslint sort-keys: ["error", "asc"] */ +/* eslint sort-keys: ["error", "asc"] -- More readable for long list */ const { LazyLoadingRuleMap } = require("./utils/lazy-loading-rule-map"); @@ -221,6 +221,7 @@ module.exports = new LazyLoadingRuleMap(Object.entries({ "no-unsafe-optional-chaining": () => require("./no-unsafe-optional-chaining"), "no-unused-expressions": () => require("./no-unused-expressions"), "no-unused-labels": () => require("./no-unused-labels"), + "no-unused-private-class-members": () => require("./no-unused-private-class-members"), "no-unused-vars": () => require("./no-unused-vars"), "no-use-before-define": () => require("./no-use-before-define"), "no-useless-backreference": () => require("./no-useless-backreference"), diff --git a/eslint/lib/rules/init-declarations.js b/eslint/lib/rules/init-declarations.js index 6cfdf92..d994bbc 100644 --- a/eslint/lib/rules/init-declarations.js +++ b/eslint/lib/rules/init-declarations.js @@ -48,7 +48,6 @@ module.exports = { docs: { description: "require or disallow initialization in variable declarations", - category: "Variables", recommended: false, url: "https://eslint.org/docs/rules/init-declarations" }, diff --git a/eslint/lib/rules/jsx-quotes.js b/eslint/lib/rules/jsx-quotes.js index 3b282df..cbadc19 100644 --- a/eslint/lib/rules/jsx-quotes.js +++ b/eslint/lib/rules/jsx-quotes.js @@ -42,7 +42,6 @@ module.exports = { docs: { description: "enforce the consistent use of either double or single quotes in JSX attributes", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/jsx-quotes" }, diff --git a/eslint/lib/rules/key-spacing.js b/eslint/lib/rules/key-spacing.js index fc885a1..c09cebb 100644 --- a/eslint/lib/rules/key-spacing.js +++ b/eslint/lib/rules/key-spacing.js @@ -139,7 +139,6 @@ module.exports = { docs: { description: "enforce consistent spacing between keys and values in object literal properties", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/key-spacing" }, @@ -428,19 +427,7 @@ module.exports = { * @returns {void} */ function report(property, side, whitespace, expected, mode) { - const diff = whitespace.length - expected, - nextColon = getNextColon(property.key), - tokenBeforeColon = sourceCode.getTokenBefore(nextColon, { includeComments: true }), - tokenAfterColon = sourceCode.getTokenAfter(nextColon, { includeComments: true }), - isKeySide = side === "key", - isExtra = diff > 0, - diffAbs = Math.abs(diff), - spaces = Array(diffAbs + 1).join(" "); - - const locStart = isKeySide ? tokenBeforeColon.loc.end : nextColon.loc.start; - const locEnd = isKeySide ? nextColon.loc.start : tokenAfterColon.loc.start; - const missingLoc = isKeySide ? tokenBeforeColon.loc : tokenAfterColon.loc; - const loc = isExtra ? { start: locStart, end: locEnd } : missingLoc; + const diff = whitespace.length - expected; if (( diff && mode === "strict" || @@ -448,6 +435,19 @@ module.exports = { diff > 0 && !expected && mode === "minimum") && !(expected && containsLineTerminator(whitespace)) ) { + const nextColon = getNextColon(property.key), + tokenBeforeColon = sourceCode.getTokenBefore(nextColon, { includeComments: true }), + tokenAfterColon = sourceCode.getTokenAfter(nextColon, { includeComments: true }), + isKeySide = side === "key", + isExtra = diff > 0, + diffAbs = Math.abs(diff), + spaces = Array(diffAbs + 1).join(" "); + + const locStart = isKeySide ? tokenBeforeColon.loc.end : nextColon.loc.start; + const locEnd = isKeySide ? nextColon.loc.start : tokenAfterColon.loc.start; + const missingLoc = isKeySide ? tokenBeforeColon.loc : tokenAfterColon.loc; + const loc = isExtra ? { start: locStart, end: locEnd } : missingLoc; + let fix; if (isExtra) { @@ -531,8 +531,8 @@ module.exports = { /** * Creates groups of properties. - * @param {ASTNode} node ObjectExpression node being evaluated. - * @returns {Array.} Groups of property AST node lists. + * @param {ASTNode} node ObjectExpression node being evaluated. + * @returns {Array} Groups of property AST node lists. */ function createGroups(node) { if (node.properties.length === 1) { @@ -600,7 +600,7 @@ module.exports = { /** * Verifies spacing of property conforms to specified options. - * @param {ASTNode} node Property node being evaluated. + * @param {ASTNode} node Property node being evaluated. * @param {Object} lineOptions Configured singleLine or multiLine options * @returns {void} */ @@ -629,7 +629,7 @@ module.exports = { /** * Verifies vertical alignment, taking into account groups of properties. - * @param {ASTNode} node ObjectExpression node being evaluated. + * @param {ASTNode} node ObjectExpression node being evaluated. * @returns {void} */ function verifyAlignment(node) { diff --git a/eslint/lib/rules/keyword-spacing.js b/eslint/lib/rules/keyword-spacing.js index 913cf46..44222e1 100644 --- a/eslint/lib/rules/keyword-spacing.js +++ b/eslint/lib/rules/keyword-spacing.js @@ -22,7 +22,7 @@ const PREV_TOKEN_M = /^[)\]}>*]$/u; const NEXT_TOKEN_M = /^[{*]$/u; const TEMPLATE_OPEN_PAREN = /\$\{$/u; const TEMPLATE_CLOSE_PAREN = /^\}/u; -const CHECK_TYPE = /^(?:JSXElement|RegularExpression|String|Template)$/u; +const CHECK_TYPE = /^(?:JSXElement|RegularExpression|String|Template|PrivateIdentifier)$/u; const KEYS = keywords.concat(["as", "async", "await", "from", "get", "let", "of", "set", "yield"]); // check duplications. @@ -67,7 +67,6 @@ module.exports = { docs: { description: "enforce consistent spacing before and after keywords", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/keyword-spacing" }, @@ -110,6 +109,8 @@ module.exports = { create(context) { const sourceCode = context.getSourceCode(); + const tokensToIgnore = new WeakSet(); + /** * Reports a given token if there are not space(s) before the token. * @param {Token} token A token to report. @@ -122,6 +123,7 @@ module.exports = { if (prevToken && (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) && !isOpenParenOfTemplate(prevToken) && + !tokensToIgnore.has(prevToken) && astUtils.isTokenOnSameLine(prevToken, token) && !sourceCode.isSpaceBetweenTokens(prevToken, token) ) { @@ -148,6 +150,7 @@ module.exports = { if (prevToken && (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) && !isOpenParenOfTemplate(prevToken) && + !tokensToIgnore.has(prevToken) && astUtils.isTokenOnSameLine(prevToken, token) && sourceCode.isSpaceBetweenTokens(prevToken, token) ) { @@ -174,6 +177,7 @@ module.exports = { if (nextToken && (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) && !isCloseParenOfTemplate(nextToken) && + !tokensToIgnore.has(nextToken) && astUtils.isTokenOnSameLine(token, nextToken) && !sourceCode.isSpaceBetweenTokens(token, nextToken) ) { @@ -200,6 +204,7 @@ module.exports = { if (nextToken && (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) && !isCloseParenOfTemplate(nextToken) && + !tokensToIgnore.has(nextToken) && astUtils.isTokenOnSameLine(token, nextToken) && sourceCode.isSpaceBetweenTokens(token, nextToken) ) { @@ -403,7 +408,15 @@ module.exports = { */ function checkSpacingForForInStatement(node) { checkSpacingAroundFirstToken(node); - checkSpacingAroundTokenBefore(node.right); + + const inToken = sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken); + const previousToken = sourceCode.getTokenBefore(inToken); + + if (previousToken.type !== "PrivateIdentifier") { + checkSpacingBefore(inToken); + } + + checkSpacingAfter(inToken); } /** @@ -419,7 +432,15 @@ module.exports = { } else { checkSpacingAroundFirstToken(node); } - checkSpacingAround(sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken)); + + const ofToken = sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken); + const previousToken = sourceCode.getTokenBefore(ofToken); + + if (previousToken.type !== "PrivateIdentifier") { + checkSpacingBefore(ofToken); + } + + checkSpacingAfter(ofToken); } /** @@ -473,6 +494,7 @@ module.exports = { * Reports `static`, `get`, and `set` keywords of a given node if usage of * spacing around those keywords is invalid. * @param {ASTNode} node A node to report. + * @throws {Error} If unable to find token get, set, or async beside method name. * @returns {void} */ function checkSpacingForProperty(node) { @@ -567,7 +589,16 @@ module.exports = { // Others ImportNamespaceSpecifier: checkSpacingForImportNamespaceSpecifier, MethodDefinition: checkSpacingForProperty, - Property: checkSpacingForProperty + PropertyDefinition: checkSpacingForProperty, + StaticBlock: checkSpacingAroundFirstToken, + Property: checkSpacingForProperty, + + // To avoid conflicts with `space-infix-ops`, e.g. `a > this.b` + "BinaryExpression[operator='>']"(node) { + const operatorToken = sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken); + + tokensToIgnore.add(operatorToken); + } }; } }; diff --git a/eslint/lib/rules/line-comment-position.js b/eslint/lib/rules/line-comment-position.js index 77ee147..ad109a4 100644 --- a/eslint/lib/rules/line-comment-position.js +++ b/eslint/lib/rules/line-comment-position.js @@ -16,7 +16,6 @@ module.exports = { docs: { description: "enforce position of line comments", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/line-comment-position" }, diff --git a/eslint/lib/rules/linebreak-style.js b/eslint/lib/rules/linebreak-style.js index b3b393e..92996eb 100644 --- a/eslint/lib/rules/linebreak-style.js +++ b/eslint/lib/rules/linebreak-style.js @@ -21,7 +21,6 @@ module.exports = { docs: { description: "enforce consistent linebreak style", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/linebreak-style" }, diff --git a/eslint/lib/rules/lines-around-comment.js b/eslint/lib/rules/lines-around-comment.js index 6806e79..513d196 100644 --- a/eslint/lib/rules/lines-around-comment.js +++ b/eslint/lib/rules/lines-around-comment.js @@ -55,7 +55,6 @@ module.exports = { docs: { description: "require empty lines around comments", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/lines-around-comment" }, @@ -186,10 +185,39 @@ module.exports = { /** * Returns the parent node that contains the given token. * @param {token} token The token to check. - * @returns {ASTNode} The parent node that contains the given token. + * @returns {ASTNode|null} The parent node that contains the given token. */ function getParentNodeOfToken(token) { - return sourceCode.getNodeByRangeIndex(token.range[0]); + const node = sourceCode.getNodeByRangeIndex(token.range[0]); + + /* + * For the purpose of this rule, the comment token is in a `StaticBlock` node only + * if it's inside the braces of that `StaticBlock` node. + * + * Example where this function returns `null`: + * + * static + * // comment + * { + * } + * + * Example where this function returns `StaticBlock` node: + * + * static + * { + * // comment + * } + * + */ + if (node && node.type === "StaticBlock") { + const openingBrace = sourceCode.getFirstToken(node, { skip: 1 }); // skip the `static` token + + return token.range[0] >= openingBrace.range[0] + ? node + : null; + } + + return node; } /** @@ -201,8 +229,15 @@ module.exports = { function isCommentAtParentStart(token, nodeType) { const parent = getParentNodeOfToken(token); - return parent && isParentNodeType(parent, nodeType) && - token.loc.start.line - parent.loc.start.line === 1; + if (parent && isParentNodeType(parent, nodeType)) { + const parentStartNodeOrToken = parent.type === "StaticBlock" + ? sourceCode.getFirstToken(parent, { skip: 1 }) // opening brace of the static block + : parent; + + return token.loc.start.line - parentStartNodeOrToken.loc.start.line === 1; + } + + return false; } /** @@ -214,7 +249,7 @@ module.exports = { function isCommentAtParentEnd(token, nodeType) { const parent = getParentNodeOfToken(token); - return parent && isParentNodeType(parent, nodeType) && + return !!parent && isParentNodeType(parent, nodeType) && parent.loc.end.line - token.loc.end.line === 1; } @@ -224,7 +259,12 @@ module.exports = { * @returns {boolean} True if the comment is at block start. */ function isCommentAtBlockStart(token) { - return isCommentAtParentStart(token, "ClassBody") || isCommentAtParentStart(token, "BlockStatement") || isCommentAtParentStart(token, "SwitchCase"); + return ( + isCommentAtParentStart(token, "ClassBody") || + isCommentAtParentStart(token, "BlockStatement") || + isCommentAtParentStart(token, "StaticBlock") || + isCommentAtParentStart(token, "SwitchCase") + ); } /** @@ -233,7 +273,13 @@ module.exports = { * @returns {boolean} True if the comment is at block end. */ function isCommentAtBlockEnd(token) { - return isCommentAtParentEnd(token, "ClassBody") || isCommentAtParentEnd(token, "BlockStatement") || isCommentAtParentEnd(token, "SwitchCase") || isCommentAtParentEnd(token, "SwitchStatement"); + return ( + isCommentAtParentEnd(token, "ClassBody") || + isCommentAtParentEnd(token, "BlockStatement") || + isCommentAtParentEnd(token, "StaticBlock") || + isCommentAtParentEnd(token, "SwitchCase") || + isCommentAtParentEnd(token, "SwitchStatement") + ); } /** diff --git a/eslint/lib/rules/lines-around-directive.js b/eslint/lib/rules/lines-around-directive.js index fb439da..c0c70e1 100644 --- a/eslint/lib/rules/lines-around-directive.js +++ b/eslint/lib/rules/lines-around-directive.js @@ -1,7 +1,7 @@ /** * @fileoverview Require or disallow newlines around directives. * @author Kai Cataldo - * @deprecated + * @deprecated in ESLint v4.0.0 */ "use strict"; @@ -18,7 +18,6 @@ module.exports = { docs: { description: "require or disallow newlines around directives", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/lines-around-directive" }, diff --git a/eslint/lib/rules/lines-between-class-members.js b/eslint/lib/rules/lines-between-class-members.js index 9723530..e4c05f3 100644 --- a/eslint/lib/rules/lines-between-class-members.js +++ b/eslint/lib/rules/lines-between-class-members.js @@ -4,6 +4,10 @@ */ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ @@ -16,7 +20,6 @@ module.exports = { docs: { description: "require or disallow an empty line between class members", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/lines-between-class-members" }, @@ -53,6 +56,51 @@ module.exports = { const sourceCode = context.getSourceCode(); + /** + * Gets a pair of tokens that should be used to check lines between two class member nodes. + * + * In most cases, this returns the very last token of the current node and + * the very first token of the next node. + * For example: + * + * class C { + * x = 1; // curLast: `;` nextFirst: `in` + * in = 2 + * } + * + * There is only one exception. If the given node ends with a semicolon, and it looks like + * a semicolon-less style's semicolon - one that is not on the same line as the preceding + * token, but is on the line where the next class member starts - this returns the preceding + * token and the semicolon as boundary tokens. + * For example: + * + * class C { + * x = 1 // curLast: `1` nextFirst: `;` + * ;in = 2 + * } + * When determining the desired layout of the code, we should treat this semicolon as + * a part of the next class member node instead of the one it technically belongs to. + * @param {ASTNode} curNode Current class member node. + * @param {ASTNode} nextNode Next class member node. + * @returns {Token} The actual last token of `node`. + * @private + */ + function getBoundaryTokens(curNode, nextNode) { + const lastToken = sourceCode.getLastToken(curNode); + const prevToken = sourceCode.getTokenBefore(lastToken); + const nextToken = sourceCode.getFirstToken(nextNode); // skip possible lone `;` between nodes + + const isSemicolonLessStyle = ( + astUtils.isSemicolonToken(lastToken) && + !astUtils.isTokenOnSameLine(prevToken, lastToken) && + astUtils.isTokenOnSameLine(lastToken, nextToken) + ); + + return isSemicolonLessStyle + ? { curLast: prevToken, nextFirst: lastToken } + : { curLast: lastToken, nextFirst: nextToken }; + } + /** * Return the last token among the consecutive tokens that have no exceed max line difference in between, before the first token in the next member. * @param {Token} prevLastToken The last token in the previous member node. @@ -101,8 +149,7 @@ module.exports = { for (let i = 0; i < body.length - 1; i++) { const curFirst = sourceCode.getFirstToken(body[i]); - const curLast = sourceCode.getLastToken(body[i]); - const nextFirst = sourceCode.getFirstToken(body[i + 1]); + const { curLast, nextFirst } = getBoundaryTokens(body[i], body[i + 1]); const isMulti = !astUtils.isTokenOnSameLine(curFirst, curLast); const skip = !isMulti && options[1].exceptAfterSingleLine; const beforePadding = findLastConsecutiveTokenAfter(curLast, nextFirst, 1); diff --git a/eslint/lib/rules/max-classes-per-file.js b/eslint/lib/rules/max-classes-per-file.js index bb48a54..3d26108 100644 --- a/eslint/lib/rules/max-classes-per-file.js +++ b/eslint/lib/rules/max-classes-per-file.js @@ -19,15 +19,31 @@ module.exports = { docs: { description: "enforce a maximum number of classes per file", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/max-classes-per-file" }, schema: [ { - type: "integer", - minimum: 1 + oneOf: [ + { + type: "integer", + minimum: 1 + }, + { + type: "object", + properties: { + ignoreExpressions: { + type: "boolean" + }, + max: { + type: "integer", + minimum: 1 + } + }, + additionalProperties: false + } + ] } ], @@ -36,8 +52,10 @@ module.exports = { } }, create(context) { - - const maxClasses = context.options[0] || 1; + const [option = {}] = context.options; + const [ignoreExpressions, max] = typeof option === "number" + ? [false, option || 1] + : [option.ignoreExpressions, option.max || 1]; let classCount = 0; @@ -46,19 +64,24 @@ module.exports = { classCount = 0; }, "Program:exit"(node) { - if (classCount > maxClasses) { + if (classCount > max) { context.report({ node, messageId: "maximumExceeded", data: { classCount, - max: maxClasses + max } }); } }, - "ClassDeclaration, ClassExpression"() { + "ClassDeclaration"() { classCount++; + }, + "ClassExpression"() { + if (!ignoreExpressions) { + classCount++; + } } }; } diff --git a/eslint/lib/rules/max-depth.js b/eslint/lib/rules/max-depth.js index 5c5296b..bdacc66 100644 --- a/eslint/lib/rules/max-depth.js +++ b/eslint/lib/rules/max-depth.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "enforce a maximum depth that blocks can be nested", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/max-depth" }, @@ -119,6 +118,7 @@ module.exports = { FunctionDeclaration: startFunction, FunctionExpression: startFunction, ArrowFunctionExpression: startFunction, + StaticBlock: startFunction, IfStatement(node) { if (node.parent.type !== "IfStatement") { @@ -147,6 +147,7 @@ module.exports = { "FunctionDeclaration:exit": endFunction, "FunctionExpression:exit": endFunction, "ArrowFunctionExpression:exit": endFunction, + "StaticBlock:exit": endFunction, "Program:exit": endFunction }; diff --git a/eslint/lib/rules/max-len.js b/eslint/lib/rules/max-len.js index dd76760..8c7985d 100644 --- a/eslint/lib/rules/max-len.js +++ b/eslint/lib/rules/max-len.js @@ -69,7 +69,6 @@ module.exports = { docs: { description: "enforce a maximum line length", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/max-len" }, @@ -215,7 +214,7 @@ module.exports = { * Ensure that an array exists at [key] on `object`, and add `value` to it. * @param {Object} object the object to mutate * @param {string} key the object's key - * @param {*} value the value to add + * @param {any} value the value to add * @returns {void} * @private */ diff --git a/eslint/lib/rules/max-lines-per-function.js b/eslint/lib/rules/max-lines-per-function.js index 60e2e87..b2130ca 100644 --- a/eslint/lib/rules/max-lines-per-function.js +++ b/eslint/lib/rules/max-lines-per-function.js @@ -48,7 +48,7 @@ const OPTIONS_OR_INTEGER_SCHEMA = { /** * Given a list of comment nodes, return a map with numeric keys (source code line numbers) and comment token values. * @param {Array} comments An array of comment nodes. - * @returns {Map.} A map with numeric keys (source code line numbers) and comment token values. + * @returns {Map} A map with numeric keys (source code line numbers) and comment token values. */ function getCommentLineNumbers(comments) { const map = new Map(); @@ -71,7 +71,6 @@ module.exports = { docs: { description: "enforce a maximum number of lines of code in a function", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/max-lines-per-function" }, diff --git a/eslint/lib/rules/max-lines.js b/eslint/lib/rules/max-lines.js index 8bd5a1c..291d7d9 100644 --- a/eslint/lib/rules/max-lines.js +++ b/eslint/lib/rules/max-lines.js @@ -34,7 +34,6 @@ module.exports = { docs: { description: "enforce a maximum number of lines per file", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/max-lines" }, @@ -137,20 +136,6 @@ module.exports = { return []; } - /** - * Returns a new array formed by applying a given callback function to each element of the array, and then flattening the result by one level. - * TODO(stephenwade): Replace this with array.flatMap when we drop support for Node v10 - * @param {any[]} array The array to process - * @param {Function} fn The function to use - * @returns {any[]} The result array - */ - function flatMap(array, fn) { - const mapped = array.map(fn); - const flattened = [].concat(...mapped); - - return flattened; - } - return { "Program:exit"() { let lines = sourceCode.lines.map((text, i) => ({ @@ -173,7 +158,7 @@ module.exports = { if (skipComments) { const comments = sourceCode.getAllComments(); - const commentLines = flatMap(comments, comment => getLinesWithoutCode(comment)); + const commentLines = comments.flatMap(getLinesWithoutCode); lines = lines.filter( l => !commentLines.includes(l.lineNumber) diff --git a/eslint/lib/rules/max-nested-callbacks.js b/eslint/lib/rules/max-nested-callbacks.js index df1bace..df24a96 100644 --- a/eslint/lib/rules/max-nested-callbacks.js +++ b/eslint/lib/rules/max-nested-callbacks.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "enforce a maximum depth that callbacks can be nested", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/max-nested-callbacks" }, diff --git a/eslint/lib/rules/max-params.js b/eslint/lib/rules/max-params.js index 8fb7984..c8be60e 100644 --- a/eslint/lib/rules/max-params.js +++ b/eslint/lib/rules/max-params.js @@ -22,7 +22,6 @@ module.exports = { docs: { description: "enforce a maximum number of parameters in function definitions", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/max-params" }, diff --git a/eslint/lib/rules/max-statements-per-line.js b/eslint/lib/rules/max-statements-per-line.js index 5407cff..7c74329 100644 --- a/eslint/lib/rules/max-statements-per-line.js +++ b/eslint/lib/rules/max-statements-per-line.js @@ -20,7 +20,6 @@ module.exports = { docs: { description: "enforce a maximum number of statements allowed per line", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/max-statements-per-line" }, diff --git a/eslint/lib/rules/max-statements.js b/eslint/lib/rules/max-statements.js index 65d5539..969e40b 100644 --- a/eslint/lib/rules/max-statements.js +++ b/eslint/lib/rules/max-statements.js @@ -22,7 +22,6 @@ module.exports = { docs: { description: "enforce a maximum number of statements allowed in function blocks", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/max-statements" }, @@ -124,6 +123,14 @@ module.exports = { function endFunction(node) { const count = functionStack.pop(); + /* + * This rule does not apply to class static blocks, but we have to track them so + * that stataments in them do not count as statements in the enclosing function. + */ + if (node.type === "StaticBlock") { + return; + } + if (ignoreTopLevelFunctions && functionStack.length === 0) { topLevelFunctions.push({ node, count }); } else { @@ -149,12 +156,14 @@ module.exports = { FunctionDeclaration: startFunction, FunctionExpression: startFunction, ArrowFunctionExpression: startFunction, + StaticBlock: startFunction, BlockStatement: countStatements, "FunctionDeclaration:exit": endFunction, "FunctionExpression:exit": endFunction, "ArrowFunctionExpression:exit": endFunction, + "StaticBlock:exit": endFunction, "Program:exit"() { if (topLevelFunctions.length === 1) { diff --git a/eslint/lib/rules/multiline-comment-style.js b/eslint/lib/rules/multiline-comment-style.js index 9524818..da5ee50 100644 --- a/eslint/lib/rules/multiline-comment-style.js +++ b/eslint/lib/rules/multiline-comment-style.js @@ -16,7 +16,6 @@ module.exports = { docs: { description: "enforce a particular style for multiline comments", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/multiline-comment-style" }, diff --git a/eslint/lib/rules/multiline-ternary.js b/eslint/lib/rules/multiline-ternary.js index 98360b9..6f468c8 100644 --- a/eslint/lib/rules/multiline-ternary.js +++ b/eslint/lib/rules/multiline-ternary.js @@ -17,7 +17,6 @@ module.exports = { docs: { description: "enforce newlines between operands of ternary expressions", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/multiline-ternary" }, diff --git a/eslint/lib/rules/new-cap.js b/eslint/lib/rules/new-cap.js index 4249a54..9abf337 100644 --- a/eslint/lib/rules/new-cap.js +++ b/eslint/lib/rules/new-cap.js @@ -33,7 +33,8 @@ const CAPS_ALLOWED = [ * Ensure that if the key is provided, it must be an array. * @param {Object} obj Object to check with `key`. * @param {string} key Object key to check on `obj`. - * @param {*} fallback If obj[key] is not present, this will be returned. + * @param {any} fallback If obj[key] is not present, this will be returned. + * @throws {TypeError} If key is not an own array type property of `obj`. * @returns {string[]} Returns obj[key] if it's an Array, otherwise `fallback` */ function checkArray(obj, key, fallback) { @@ -81,7 +82,6 @@ module.exports = { docs: { description: "require constructor names to begin with a capital letter", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/new-cap" }, diff --git a/eslint/lib/rules/new-parens.js b/eslint/lib/rules/new-parens.js index 405ec1b..786300d 100644 --- a/eslint/lib/rules/new-parens.js +++ b/eslint/lib/rules/new-parens.js @@ -25,7 +25,6 @@ module.exports = { docs: { description: "enforce or disallow parentheses when invoking a constructor with no arguments", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/new-parens" }, diff --git a/eslint/lib/rules/newline-after-var.js b/eslint/lib/rules/newline-after-var.js index 4809d9b..3eea1b1 100644 --- a/eslint/lib/rules/newline-after-var.js +++ b/eslint/lib/rules/newline-after-var.js @@ -1,7 +1,7 @@ /** * @fileoverview Rule to check empty newline after "var" statement * @author Gopal Venkatesan - * @deprecated + * @deprecated in ESLint v4.0.0 */ "use strict"; @@ -22,7 +22,6 @@ module.exports = { docs: { description: "require or disallow an empty line after variable declarations", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/newline-after-var" }, @@ -145,9 +144,9 @@ module.exports = { /** * Determine if a token starts more than one line after a comment ends - * @param {token} token The token being checked - * @param {integer} commentStartLine The line number on which the comment starts - * @returns {boolean} True if `token` does not start immediately after a comment + * @param {token} token The token being checked + * @param {integer} commentStartLine The line number on which the comment starts + * @returns {boolean} True if `token` does not start immediately after a comment */ function hasBlankLineAfterComment(token, commentStartLine) { return token.loc.start.line > getLastCommentLineOfBlock(commentStartLine) + 1; diff --git a/eslint/lib/rules/newline-before-return.js b/eslint/lib/rules/newline-before-return.js index 65ca323..fd6341e 100644 --- a/eslint/lib/rules/newline-before-return.js +++ b/eslint/lib/rules/newline-before-return.js @@ -1,7 +1,7 @@ /** * @fileoverview Rule to require newlines before `return` statement * @author Kai Cataldo - * @deprecated + * @deprecated in ESLint v4.0.0 */ "use strict"; @@ -15,7 +15,6 @@ module.exports = { docs: { description: "require an empty line before `return` statements", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/newline-before-return" }, diff --git a/eslint/lib/rules/newline-per-chained-call.js b/eslint/lib/rules/newline-per-chained-call.js index 46c9d6c..8de9a6a 100644 --- a/eslint/lib/rules/newline-per-chained-call.js +++ b/eslint/lib/rules/newline-per-chained-call.js @@ -18,7 +18,6 @@ module.exports = { docs: { description: "require a newline after each call in a method chain", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/newline-per-chained-call" }, @@ -53,7 +52,7 @@ module.exports = { * Get the prefix of a given MemberExpression node. * If the MemberExpression node is a computed value it returns a * left bracket. If not it returns a period. - * @param {ASTNode} node A MemberExpression node to get + * @param {ASTNode} node A MemberExpression node to get * @returns {string} The prefix of the node. */ function getPrefix(node) { diff --git a/eslint/lib/rules/no-alert.js b/eslint/lib/rules/no-alert.js index 702b4d2..918b984 100644 --- a/eslint/lib/rules/no-alert.js +++ b/eslint/lib/rules/no-alert.js @@ -88,7 +88,6 @@ module.exports = { docs: { description: "disallow the use of `alert`, `confirm`, and `prompt`", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-alert" }, diff --git a/eslint/lib/rules/no-array-constructor.js b/eslint/lib/rules/no-array-constructor.js index 90c6d6b..0904fa6 100644 --- a/eslint/lib/rules/no-array-constructor.js +++ b/eslint/lib/rules/no-array-constructor.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "disallow `Array` constructors", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/no-array-constructor" }, diff --git a/eslint/lib/rules/no-async-promise-executor.js b/eslint/lib/rules/no-async-promise-executor.js index 553311e..27116f1 100644 --- a/eslint/lib/rules/no-async-promise-executor.js +++ b/eslint/lib/rules/no-async-promise-executor.js @@ -14,7 +14,6 @@ module.exports = { docs: { description: "disallow using an async function as a Promise executor", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-async-promise-executor" }, diff --git a/eslint/lib/rules/no-await-in-loop.js b/eslint/lib/rules/no-await-in-loop.js index 9ca8986..38af8b5 100644 --- a/eslint/lib/rules/no-await-in-loop.js +++ b/eslint/lib/rules/no-await-in-loop.js @@ -59,7 +59,6 @@ module.exports = { docs: { description: "disallow `await` inside of loops", - category: "Possible Errors", recommended: false, url: "https://eslint.org/docs/rules/no-await-in-loop" }, diff --git a/eslint/lib/rules/no-bitwise.js b/eslint/lib/rules/no-bitwise.js index a9c3360..10bf24a 100644 --- a/eslint/lib/rules/no-bitwise.js +++ b/eslint/lib/rules/no-bitwise.js @@ -26,7 +26,6 @@ module.exports = { docs: { description: "disallow bitwise operators", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/no-bitwise" }, @@ -63,7 +62,7 @@ module.exports = { /** * Reports an unexpected use of a bitwise operator. - * @param {ASTNode} node Node which contains the bitwise operator. + * @param {ASTNode} node Node which contains the bitwise operator. * @returns {void} */ function report(node) { @@ -72,7 +71,7 @@ module.exports = { /** * Checks if the given node has a bitwise operator. - * @param {ASTNode} node The node to check. + * @param {ASTNode} node The node to check. * @returns {boolean} Whether or not the node has a bitwise operator. */ function hasBitwiseOperator(node) { @@ -81,7 +80,7 @@ module.exports = { /** * Checks if exceptions were provided, e.g. `{ allow: ['~', '|'] }`. - * @param {ASTNode} node The node to check. + * @param {ASTNode} node The node to check. * @returns {boolean} Whether or not the node has a bitwise operator. */ function allowedOperator(node) { @@ -90,7 +89,7 @@ module.exports = { /** * Checks if the given bitwise operator is used for integer typecasting, i.e. "|0" - * @param {ASTNode} node The node to check. + * @param {ASTNode} node The node to check. * @returns {boolean} whether the node is used in integer typecasting. */ function isInt32Hint(node) { @@ -100,7 +99,7 @@ module.exports = { /** * Report if the given node contains a bitwise operator. - * @param {ASTNode} node The node to check. + * @param {ASTNode} node The node to check. * @returns {void} */ function checkNodeForBitwiseOperator(node) { diff --git a/eslint/lib/rules/no-buffer-constructor.js b/eslint/lib/rules/no-buffer-constructor.js index 152dda0..cc5906e 100644 --- a/eslint/lib/rules/no-buffer-constructor.js +++ b/eslint/lib/rules/no-buffer-constructor.js @@ -1,6 +1,7 @@ /** * @fileoverview disallow use of the Buffer() constructor * @author Teddy Katz + * @deprecated in ESLint v7.0.0 */ "use strict"; @@ -18,7 +19,6 @@ module.exports = { docs: { description: "disallow use of the `Buffer()` constructor", - category: "Node.js and CommonJS", recommended: false, url: "https://eslint.org/docs/rules/no-buffer-constructor" }, diff --git a/eslint/lib/rules/no-caller.js b/eslint/lib/rules/no-caller.js index 5fe1bd4..dbb5279 100644 --- a/eslint/lib/rules/no-caller.js +++ b/eslint/lib/rules/no-caller.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "disallow the use of `arguments.caller` or `arguments.callee`", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-caller" }, diff --git a/eslint/lib/rules/no-case-declarations.js b/eslint/lib/rules/no-case-declarations.js index 1d54e22..a132f03 100644 --- a/eslint/lib/rules/no-case-declarations.js +++ b/eslint/lib/rules/no-case-declarations.js @@ -14,7 +14,6 @@ module.exports = { docs: { description: "disallow lexical declarations in case clauses", - category: "Best Practices", recommended: true, url: "https://eslint.org/docs/rules/no-case-declarations" }, diff --git a/eslint/lib/rules/no-catch-shadow.js b/eslint/lib/rules/no-catch-shadow.js index 4917af8..0cbeedf 100644 --- a/eslint/lib/rules/no-catch-shadow.js +++ b/eslint/lib/rules/no-catch-shadow.js @@ -22,7 +22,6 @@ module.exports = { docs: { description: "disallow `catch` clause parameters from shadowing variables in the outer scope", - category: "Variables", recommended: false, url: "https://eslint.org/docs/rules/no-catch-shadow" }, diff --git a/eslint/lib/rules/no-class-assign.js b/eslint/lib/rules/no-class-assign.js index 887058b..839ad03 100644 --- a/eslint/lib/rules/no-class-assign.js +++ b/eslint/lib/rules/no-class-assign.js @@ -17,7 +17,6 @@ module.exports = { docs: { description: "disallow reassigning class members", - category: "ECMAScript 6", recommended: true, url: "https://eslint.org/docs/rules/no-class-assign" }, diff --git a/eslint/lib/rules/no-compare-neg-zero.js b/eslint/lib/rules/no-compare-neg-zero.js index 0c6865a..e8fdaa0 100644 --- a/eslint/lib/rules/no-compare-neg-zero.js +++ b/eslint/lib/rules/no-compare-neg-zero.js @@ -14,7 +14,6 @@ module.exports = { docs: { description: "disallow comparing against -0", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-compare-neg-zero" }, diff --git a/eslint/lib/rules/no-cond-assign.js b/eslint/lib/rules/no-cond-assign.js index 3843a7a..42f75af 100644 --- a/eslint/lib/rules/no-cond-assign.js +++ b/eslint/lib/rules/no-cond-assign.js @@ -34,7 +34,6 @@ module.exports = { docs: { description: "disallow assignment operators in conditional expressions", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-cond-assign" }, diff --git a/eslint/lib/rules/no-confusing-arrow.js b/eslint/lib/rules/no-confusing-arrow.js index 9009b64..fa87f40 100644 --- a/eslint/lib/rules/no-confusing-arrow.js +++ b/eslint/lib/rules/no-confusing-arrow.js @@ -31,7 +31,6 @@ module.exports = { docs: { description: "disallow arrow functions where they could be confused with comparisons", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/no-confusing-arrow" }, diff --git a/eslint/lib/rules/no-console.js b/eslint/lib/rules/no-console.js index 56dbbc3..a5937cb 100644 --- a/eslint/lib/rules/no-console.js +++ b/eslint/lib/rules/no-console.js @@ -21,7 +21,6 @@ module.exports = { docs: { description: "disallow the use of `console`", - category: "Possible Errors", recommended: false, url: "https://eslint.org/docs/rules/no-console" }, diff --git a/eslint/lib/rules/no-const-assign.js b/eslint/lib/rules/no-const-assign.js index e4ae891..6ca1b61 100644 --- a/eslint/lib/rules/no-const-assign.js +++ b/eslint/lib/rules/no-const-assign.js @@ -17,7 +17,6 @@ module.exports = { docs: { description: "disallow reassigning `const` variables", - category: "ECMAScript 6", recommended: true, url: "https://eslint.org/docs/rules/no-const-assign" }, diff --git a/eslint/lib/rules/no-constant-condition.js b/eslint/lib/rules/no-constant-condition.js index 3c2d68c..7a7030a 100644 --- a/eslint/lib/rules/no-constant-condition.js +++ b/eslint/lib/rules/no-constant-condition.js @@ -19,7 +19,6 @@ module.exports = { docs: { description: "disallow constant expressions in conditions", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-constant-condition" }, diff --git a/eslint/lib/rules/no-constructor-return.js b/eslint/lib/rules/no-constructor-return.js index 4757770..b4b5baf 100644 --- a/eslint/lib/rules/no-constructor-return.js +++ b/eslint/lib/rules/no-constructor-return.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "disallow returning value from constructor", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-constructor-return" }, diff --git a/eslint/lib/rules/no-continue.js b/eslint/lib/rules/no-continue.js index 96718d1..e72e862 100644 --- a/eslint/lib/rules/no-continue.js +++ b/eslint/lib/rules/no-continue.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "disallow `continue` statements", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/no-continue" }, diff --git a/eslint/lib/rules/no-control-regex.js b/eslint/lib/rules/no-control-regex.js index 6feeb64..908d61a 100644 --- a/eslint/lib/rules/no-control-regex.js +++ b/eslint/lib/rules/no-control-regex.js @@ -52,7 +52,6 @@ module.exports = { docs: { description: "disallow control characters in regular expressions", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-control-regex" }, diff --git a/eslint/lib/rules/no-debugger.js b/eslint/lib/rules/no-debugger.js index 95a28a8..46dd576 100644 --- a/eslint/lib/rules/no-debugger.js +++ b/eslint/lib/rules/no-debugger.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "disallow the use of `debugger`", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-debugger" }, diff --git a/eslint/lib/rules/no-delete-var.js b/eslint/lib/rules/no-delete-var.js index aeab951..1438ebc 100644 --- a/eslint/lib/rules/no-delete-var.js +++ b/eslint/lib/rules/no-delete-var.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "disallow deleting variables", - category: "Variables", recommended: true, url: "https://eslint.org/docs/rules/no-delete-var" }, diff --git a/eslint/lib/rules/no-div-regex.js b/eslint/lib/rules/no-div-regex.js index 0ccabdc..40388c3 100644 --- a/eslint/lib/rules/no-div-regex.js +++ b/eslint/lib/rules/no-div-regex.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "disallow division operators explicitly at the beginning of regular expressions", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-div-regex" }, diff --git a/eslint/lib/rules/no-dupe-args.js b/eslint/lib/rules/no-dupe-args.js index 817277f..0880b9c 100644 --- a/eslint/lib/rules/no-dupe-args.js +++ b/eslint/lib/rules/no-dupe-args.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "disallow duplicate arguments in `function` definitions", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-dupe-args" }, diff --git a/eslint/lib/rules/no-dupe-class-members.js b/eslint/lib/rules/no-dupe-class-members.js index b12939d..f74865b 100644 --- a/eslint/lib/rules/no-dupe-class-members.js +++ b/eslint/lib/rules/no-dupe-class-members.js @@ -17,7 +17,6 @@ module.exports = { docs: { description: "disallow duplicate class members", - category: "ECMAScript 6", recommended: true, url: "https://eslint.org/docs/rules/no-dupe-class-members" }, @@ -73,20 +72,21 @@ module.exports = { }, // Reports the node if its name has been declared already. - MethodDefinition(node) { + "MethodDefinition, PropertyDefinition"(node) { const name = astUtils.getStaticPropertyName(node); + const kind = node.type === "MethodDefinition" ? node.kind : "field"; - if (name === null || node.kind === "constructor") { + if (name === null || kind === "constructor") { return; } const state = getState(name, node.static); let isDuplicate = false; - if (node.kind === "get") { + if (kind === "get") { isDuplicate = (state.init || state.get); state.get = true; - } else if (node.kind === "set") { + } else if (kind === "set") { isDuplicate = (state.init || state.set); state.set = true; } else { diff --git a/eslint/lib/rules/no-dupe-else-if.js b/eslint/lib/rules/no-dupe-else-if.js index cbeb437..0d8b17c 100644 --- a/eslint/lib/rules/no-dupe-else-if.js +++ b/eslint/lib/rules/no-dupe-else-if.js @@ -52,7 +52,6 @@ module.exports = { docs: { description: "disallow duplicate conditions in if-else-if chains", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-dupe-else-if" }, diff --git a/eslint/lib/rules/no-dupe-keys.js b/eslint/lib/rules/no-dupe-keys.js index 89e1f2d..ecec022 100644 --- a/eslint/lib/rules/no-dupe-keys.js +++ b/eslint/lib/rules/no-dupe-keys.js @@ -23,7 +23,6 @@ const SET_KIND = /^(?:init|set)$/u; */ class ObjectInfo { - // eslint-disable-next-line jsdoc/require-description /** * @param {ObjectInfo|null} upper The information of the outer object. * @param {ASTNode} node The ObjectExpression node of this information. @@ -89,7 +88,6 @@ module.exports = { docs: { description: "disallow duplicate keys in object literals", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-dupe-keys" }, diff --git a/eslint/lib/rules/no-duplicate-case.js b/eslint/lib/rules/no-duplicate-case.js index e2d9665..4669dce 100644 --- a/eslint/lib/rules/no-duplicate-case.js +++ b/eslint/lib/rules/no-duplicate-case.js @@ -22,7 +22,6 @@ module.exports = { docs: { description: "disallow duplicate case labels", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-duplicate-case" }, diff --git a/eslint/lib/rules/no-duplicate-imports.js b/eslint/lib/rules/no-duplicate-imports.js index cc3da1d..2663698 100644 --- a/eslint/lib/rules/no-duplicate-imports.js +++ b/eslint/lib/rules/no-duplicate-imports.js @@ -233,7 +233,6 @@ module.exports = { docs: { description: "disallow duplicate module imports", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/no-duplicate-imports" }, diff --git a/eslint/lib/rules/no-else-return.js b/eslint/lib/rules/no-else-return.js index 84409fa..4c981ae 100644 --- a/eslint/lib/rules/no-else-return.js +++ b/eslint/lib/rules/no-else-return.js @@ -22,7 +22,6 @@ module.exports = { docs: { description: "disallow `else` blocks after `return` statements in `if` statements", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-else-return" }, diff --git a/eslint/lib/rules/no-empty-character-class.js b/eslint/lib/rules/no-empty-character-class.js index 7dc219f..85e8ef7 100644 --- a/eslint/lib/rules/no-empty-character-class.js +++ b/eslint/lib/rules/no-empty-character-class.js @@ -12,16 +12,13 @@ /* * plain-English description of the following regexp: * 0. `^` fix the match at the beginning of the string - * 1. `\/`: the `/` that begins the regexp - * 2. `([^\\[]|\\.|\[([^\\\]]|\\.)+\])*`: regexp contents; 0 or more of the following - * 2.0. `[^\\[]`: any character that's not a `\` or a `[` (anything but escape sequences and character classes) - * 2.1. `\\.`: an escape sequence - * 2.2. `\[([^\\\]]|\\.)+\]`: a character class that isn't empty - * 3. `\/` the `/` that ends the regexp - * 4. `[gimuy]*`: optional regexp flags - * 5. `$`: fix the match at the end of the string + * 1. `([^\\[]|\\.|\[([^\\\]]|\\.)+\])*`: regexp contents; 0 or more of the following + * 1.0. `[^\\[]`: any character that's not a `\` or a `[` (anything but escape sequences and character classes) + * 1.1. `\\.`: an escape sequence + * 1.2. `\[([^\\\]]|\\.)+\]`: a character class that isn't empty + * 2. `$`: fix the match at the end of the string */ -const regex = /^\/([^\\[]|\\.|\[([^\\\]]|\\.)+\])*\/[gimuys]*$/u; +const regex = /^([^\\[]|\\.|\[([^\\\]]|\\.)+\])*$/u; //------------------------------------------------------------------------------ // Rule Definition @@ -33,7 +30,6 @@ module.exports = { docs: { description: "disallow empty character classes in regular expressions", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-empty-character-class" }, @@ -46,18 +42,12 @@ module.exports = { }, create(context) { - const sourceCode = context.getSourceCode(); - return { - - Literal(node) { - const token = sourceCode.getFirstToken(node); - - if (token.type === "RegularExpression" && !regex.test(token.value)) { + "Literal[regex]"(node) { + if (!regex.test(node.regex.pattern)) { context.report({ node, messageId: "unexpected" }); } } - }; } diff --git a/eslint/lib/rules/no-empty-function.js b/eslint/lib/rules/no-empty-function.js index c512f8c..8b1073a 100644 --- a/eslint/lib/rules/no-empty-function.js +++ b/eslint/lib/rules/no-empty-function.js @@ -95,7 +95,6 @@ module.exports = { docs: { description: "disallow empty functions", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-empty-function" }, diff --git a/eslint/lib/rules/no-empty-pattern.js b/eslint/lib/rules/no-empty-pattern.js index 9f34bfd..99ea3a7 100644 --- a/eslint/lib/rules/no-empty-pattern.js +++ b/eslint/lib/rules/no-empty-pattern.js @@ -14,7 +14,6 @@ module.exports = { docs: { description: "disallow empty destructuring patterns", - category: "Best Practices", recommended: true, url: "https://eslint.org/docs/rules/no-empty-pattern" }, diff --git a/eslint/lib/rules/no-empty.js b/eslint/lib/rules/no-empty.js index 45bf03c..4ed3c5c 100644 --- a/eslint/lib/rules/no-empty.js +++ b/eslint/lib/rules/no-empty.js @@ -20,7 +20,6 @@ module.exports = { docs: { description: "disallow empty block statements", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-empty" }, diff --git a/eslint/lib/rules/no-eq-null.js b/eslint/lib/rules/no-eq-null.js index b8dead9..dae9228 100644 --- a/eslint/lib/rules/no-eq-null.js +++ b/eslint/lib/rules/no-eq-null.js @@ -16,7 +16,6 @@ module.exports = { docs: { description: "disallow `null` comparisons without type-checking operators", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-eq-null" }, diff --git a/eslint/lib/rules/no-eval.js b/eslint/lib/rules/no-eval.js index a020fde..96b85a0 100644 --- a/eslint/lib/rules/no-eval.js +++ b/eslint/lib/rules/no-eval.js @@ -43,7 +43,6 @@ module.exports = { docs: { description: "disallow the use of `eval()`", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-eval" }, @@ -247,6 +246,10 @@ module.exports = { "FunctionExpression:exit": exitVarScope, ArrowFunctionExpression: enterVarScope, "ArrowFunctionExpression:exit": exitVarScope, + "PropertyDefinition > *.value": enterVarScope, + "PropertyDefinition > *.value:exit": exitVarScope, + StaticBlock: enterVarScope, + "StaticBlock:exit": exitVarScope, ThisExpression(node) { if (!isMember(node.parent, "eval")) { diff --git a/eslint/lib/rules/no-ex-assign.js b/eslint/lib/rules/no-ex-assign.js index 1163920..cd56c94 100644 --- a/eslint/lib/rules/no-ex-assign.js +++ b/eslint/lib/rules/no-ex-assign.js @@ -17,7 +17,6 @@ module.exports = { docs: { description: "disallow reassigning exceptions in `catch` clauses", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-ex-assign" }, diff --git a/eslint/lib/rules/no-extend-native.js b/eslint/lib/rules/no-extend-native.js index 2a804b5..4d5accb 100644 --- a/eslint/lib/rules/no-extend-native.js +++ b/eslint/lib/rules/no-extend-native.js @@ -22,7 +22,6 @@ module.exports = { docs: { description: "disallow extending native types", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-extend-native" }, diff --git a/eslint/lib/rules/no-extra-bind.js b/eslint/lib/rules/no-extra-bind.js index 2db440d..6fd3be1 100644 --- a/eslint/lib/rules/no-extra-bind.js +++ b/eslint/lib/rules/no-extra-bind.js @@ -26,7 +26,6 @@ module.exports = { docs: { description: "disallow unnecessary calls to `.bind()`", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-extra-bind" }, diff --git a/eslint/lib/rules/no-extra-boolean-cast.js b/eslint/lib/rules/no-extra-boolean-cast.js index 6ae3ea6..cb061da 100644 --- a/eslint/lib/rules/no-extra-boolean-cast.js +++ b/eslint/lib/rules/no-extra-boolean-cast.js @@ -24,7 +24,6 @@ module.exports = { docs: { description: "disallow unnecessary boolean casts", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-extra-boolean-cast" }, @@ -150,6 +149,7 @@ module.exports = { * For example, if the parent is `ConditionalExpression`, `previousNode` must be its `test` child. * @param {ASTNode} previousNode Previous node. * @param {ASTNode} node The node to check. + * @throws {Error} (Unreachable.) * @returns {boolean} `true` if the node needs to be parenthesized. */ function needsParens(previousNode, node) { diff --git a/eslint/lib/rules/no-extra-label.js b/eslint/lib/rules/no-extra-label.js index 81406e7..bbb2413 100644 --- a/eslint/lib/rules/no-extra-label.js +++ b/eslint/lib/rules/no-extra-label.js @@ -21,7 +21,6 @@ module.exports = { docs: { description: "disallow unnecessary labels", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-extra-label" }, diff --git a/eslint/lib/rules/no-extra-parens.js b/eslint/lib/rules/no-extra-parens.js index 307e340..0756d2f 100644 --- a/eslint/lib/rules/no-extra-parens.js +++ b/eslint/lib/rules/no-extra-parens.js @@ -17,7 +17,6 @@ module.exports = { docs: { description: "disallow unnecessary parentheses", - category: "Possible Errors", recommended: false, url: "https://eslint.org/docs/rules/no-extra-parens" }, @@ -808,13 +807,6 @@ module.exports = { CallExpression: checkCallNew, - ClassBody(node) { - node.body - .filter(member => member.type === "MethodDefinition" && member.computed && member.key) - .filter(member => hasExcessParensWithPrecedence(member.key, PRECEDENCE_OF_ASSIGNMENT_EXPR)) - .forEach(member => report(member.key)); - }, - ConditionalExpression(node) { if (isReturnAssignException(node)) { return; @@ -1063,6 +1055,12 @@ module.exports = { } }, + "MethodDefinition[computed=true]"(node) { + if (hasExcessParensWithPrecedence(node.key, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { + report(node.key); + } + }, + NewExpression: checkCallNew, ObjectExpression(node) { @@ -1090,6 +1088,16 @@ module.exports = { } }, + PropertyDefinition(node) { + if (node.computed && hasExcessParensWithPrecedence(node.key, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { + report(node.key); + } + + if (node.value && hasExcessParensWithPrecedence(node.value, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { + report(node.value); + } + }, + RestElement(node) { const argument = node.argument; diff --git a/eslint/lib/rules/no-extra-semi.js b/eslint/lib/rules/no-extra-semi.js index e0a8df0..0e2bcaf 100644 --- a/eslint/lib/rules/no-extra-semi.js +++ b/eslint/lib/rules/no-extra-semi.js @@ -22,7 +22,6 @@ module.exports = { docs: { description: "disallow unnecessary semicolons", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-extra-semi" }, @@ -117,7 +116,7 @@ module.exports = { * @param {Node} node A MethodDefinition node of the start point. * @returns {void} */ - MethodDefinition(node) { + "MethodDefinition, PropertyDefinition, StaticBlock"(node) { checkForPartOfClassBody(sourceCode.getTokenAfter(node)); } }; diff --git a/eslint/lib/rules/no-fallthrough.js b/eslint/lib/rules/no-fallthrough.js index e8016e9..bf2c825 100644 --- a/eslint/lib/rules/no-fallthrough.js +++ b/eslint/lib/rules/no-fallthrough.js @@ -11,15 +11,26 @@ const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/iu; /** - * Checks whether or not a given node has a fallthrough comment. - * @param {ASTNode} node A SwitchCase node to get comments. + * Checks whether or not a given case has a fallthrough comment. + * @param {ASTNode} caseWhichFallsThrough SwitchCase node which falls through. + * @param {ASTNode} subsequentCase The case after caseWhichFallsThrough. * @param {RuleContext} context A rule context which stores comments. * @param {RegExp} fallthroughCommentPattern A pattern to match comment to. - * @returns {boolean} `true` if the node has a valid fallthrough comment. + * @returns {boolean} `true` if the case has a valid fallthrough comment. */ -function hasFallthroughComment(node, context, fallthroughCommentPattern) { +function hasFallthroughComment(caseWhichFallsThrough, subsequentCase, context, fallthroughCommentPattern) { const sourceCode = context.getSourceCode(); - const comment = sourceCode.getCommentsBefore(node).pop(); + + if (caseWhichFallsThrough.consequent.length === 1 && caseWhichFallsThrough.consequent[0].type === "BlockStatement") { + const trailingCloseBrace = sourceCode.getLastToken(caseWhichFallsThrough.consequent[0]); + const commentInBlock = sourceCode.getCommentsBefore(trailingCloseBrace).pop(); + + if (commentInBlock && fallthroughCommentPattern.test(commentInBlock.value)) { + return true; + } + } + + const comment = sourceCode.getCommentsBefore(subsequentCase).pop(); return Boolean(comment && fallthroughCommentPattern.test(comment.value)); } @@ -53,7 +64,6 @@ module.exports = { docs: { description: "disallow fallthrough of `case` statements", - category: "Best Practices", recommended: true, url: "https://eslint.org/docs/rules/no-fallthrough" }, @@ -108,7 +118,7 @@ module.exports = { * Checks whether or not there is a fallthrough comment. * And reports the previous fallthrough node if that does not exist. */ - if (fallthroughCase && !hasFallthroughComment(node, context, fallthroughCommentPattern)) { + if (fallthroughCase && !hasFallthroughComment(fallthroughCase, node, context, fallthroughCommentPattern)) { context.report({ messageId: node.test ? "case" : "default", node diff --git a/eslint/lib/rules/no-floating-decimal.js b/eslint/lib/rules/no-floating-decimal.js index b1d8832..92ac232 100644 --- a/eslint/lib/rules/no-floating-decimal.js +++ b/eslint/lib/rules/no-floating-decimal.js @@ -21,7 +21,6 @@ module.exports = { docs: { description: "disallow leading or trailing decimal points in numeric literals", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-floating-decimal" }, diff --git a/eslint/lib/rules/no-func-assign.js b/eslint/lib/rules/no-func-assign.js index 33d0ad9..aa04f33 100644 --- a/eslint/lib/rules/no-func-assign.js +++ b/eslint/lib/rules/no-func-assign.js @@ -17,7 +17,6 @@ module.exports = { docs: { description: "disallow reassigning `function` declarations", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-func-assign" }, diff --git a/eslint/lib/rules/no-global-assign.js b/eslint/lib/rules/no-global-assign.js index ea854c4..85aac7c 100644 --- a/eslint/lib/rules/no-global-assign.js +++ b/eslint/lib/rules/no-global-assign.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "disallow assignments to native objects or read-only global variables", - category: "Best Practices", recommended: true, url: "https://eslint.org/docs/rules/no-global-assign" }, diff --git a/eslint/lib/rules/no-implicit-coercion.js b/eslint/lib/rules/no-implicit-coercion.js index 993b8d1..1d11e10 100644 --- a/eslint/lib/rules/no-implicit-coercion.js +++ b/eslint/lib/rules/no-implicit-coercion.js @@ -173,7 +173,6 @@ module.exports = { docs: { description: "disallow shorthand type conversions", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-implicit-coercion" }, diff --git a/eslint/lib/rules/no-implicit-globals.js b/eslint/lib/rules/no-implicit-globals.js index d4bfa3a..8740cd8 100644 --- a/eslint/lib/rules/no-implicit-globals.js +++ b/eslint/lib/rules/no-implicit-globals.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "disallow declarations in the global scope", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-implicit-globals" }, diff --git a/eslint/lib/rules/no-implied-eval.js b/eslint/lib/rules/no-implied-eval.js index b8120a6..2432e68 100644 --- a/eslint/lib/rules/no-implied-eval.js +++ b/eslint/lib/rules/no-implied-eval.js @@ -22,7 +22,6 @@ module.exports = { docs: { description: "disallow the use of `eval()`-like methods", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-implied-eval" }, diff --git a/eslint/lib/rules/no-import-assign.js b/eslint/lib/rules/no-import-assign.js index 41060d8..fbe63d0 100644 --- a/eslint/lib/rules/no-import-assign.js +++ b/eslint/lib/rules/no-import-assign.js @@ -180,7 +180,6 @@ module.exports = { docs: { description: "disallow assigning to imported bindings", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-import-assign" }, diff --git a/eslint/lib/rules/no-inline-comments.js b/eslint/lib/rules/no-inline-comments.js index dec2786..8a955a6 100644 --- a/eslint/lib/rules/no-inline-comments.js +++ b/eslint/lib/rules/no-inline-comments.js @@ -16,7 +16,6 @@ module.exports = { docs: { description: "disallow inline comments after code", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/no-inline-comments" }, diff --git a/eslint/lib/rules/no-inner-declarations.js b/eslint/lib/rules/no-inner-declarations.js index 0768bc6..49b5114 100644 --- a/eslint/lib/rules/no-inner-declarations.js +++ b/eslint/lib/rules/no-inner-declarations.js @@ -15,16 +15,39 @@ const astUtils = require("./utils/ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -const validParent = new Set(["Program", "ExportNamedDeclaration", "ExportDefaultDeclaration"]); +const validParent = new Set(["Program", "StaticBlock", "ExportNamedDeclaration", "ExportDefaultDeclaration"]); const validBlockStatementParent = new Set(["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"]); +/** + * Finds the nearest enclosing context where this rule allows declarations and returns its description. + * @param {ASTNode} node Node to search from. + * @returns {string} Description. One of "program", "function body", "class static block body". + */ +function getAllowedBodyDescription(node) { + let { parent } = node; + + while (parent) { + + if (parent.type === "StaticBlock") { + return "class static block body"; + } + + if (astUtils.isFunction(parent)) { + return "function body"; + } + + ({ parent } = parent); + } + + return "program"; +} + module.exports = { meta: { type: "problem", docs: { description: "disallow variable or `function` declarations in nested blocks", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-inner-declarations" }, @@ -60,14 +83,12 @@ module.exports = { return; } - const upperFunction = astUtils.getUpperFunction(parent); - context.report({ node, messageId: "moveDeclToRoot", data: { type: (node.type === "FunctionDeclaration" ? "function" : "variable"), - body: (upperFunction === null ? "program" : "function body") + body: getAllowedBodyDescription(node) } }); } diff --git a/eslint/lib/rules/no-invalid-regexp.js b/eslint/lib/rules/no-invalid-regexp.js index 94ad5ba..ee19932 100644 --- a/eslint/lib/rules/no-invalid-regexp.js +++ b/eslint/lib/rules/no-invalid-regexp.js @@ -10,7 +10,7 @@ const RegExpValidator = require("regexpp").RegExpValidator; const validator = new RegExpValidator(); -const validFlags = /[gimuys]/gu; +const validFlags = /[dgimsuy]/gu; const undefined1 = void 0; //------------------------------------------------------------------------------ @@ -23,7 +23,6 @@ module.exports = { docs: { description: "disallow invalid regular expression strings in `RegExp` constructors", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-invalid-regexp" }, diff --git a/eslint/lib/rules/no-invalid-this.js b/eslint/lib/rules/no-invalid-this.js index a79c586..e1d7cbc 100644 --- a/eslint/lib/rules/no-invalid-this.js +++ b/eslint/lib/rules/no-invalid-this.js @@ -21,7 +21,6 @@ module.exports = { docs: { description: "disallow `this` keywords outside of classes or class-like objects", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-invalid-this" }, @@ -129,6 +128,14 @@ module.exports = { FunctionExpression: enterFunction, "FunctionExpression:exit": exitFunction, + // Field initializers are implicit functions. + "PropertyDefinition > *.value": enterFunction, + "PropertyDefinition > *.value:exit": exitFunction, + + // Class static blocks are implicit functions. + StaticBlock: enterFunction, + "StaticBlock:exit": exitFunction, + // Reports if `this` of the current context is invalid. ThisExpression(node) { const current = stack.getCurrent(); diff --git a/eslint/lib/rules/no-irregular-whitespace.js b/eslint/lib/rules/no-irregular-whitespace.js index 15711c6..c160971 100644 --- a/eslint/lib/rules/no-irregular-whitespace.js +++ b/eslint/lib/rules/no-irregular-whitespace.js @@ -31,7 +31,6 @@ module.exports = { docs: { description: "disallow irregular whitespace", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-irregular-whitespace" }, diff --git a/eslint/lib/rules/no-iterator.js b/eslint/lib/rules/no-iterator.js index 9ba1e7a..4117f62 100644 --- a/eslint/lib/rules/no-iterator.js +++ b/eslint/lib/rules/no-iterator.js @@ -21,7 +21,6 @@ module.exports = { docs: { description: "disallow the use of the `__iterator__` property", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-iterator" }, diff --git a/eslint/lib/rules/no-label-var.js b/eslint/lib/rules/no-label-var.js index 570db03..4532527 100644 --- a/eslint/lib/rules/no-label-var.js +++ b/eslint/lib/rules/no-label-var.js @@ -21,7 +21,6 @@ module.exports = { docs: { description: "disallow labels that share a name with a variable", - category: "Variables", recommended: false, url: "https://eslint.org/docs/rules/no-label-var" }, diff --git a/eslint/lib/rules/no-labels.js b/eslint/lib/rules/no-labels.js index 85760d8..5dd15be 100644 --- a/eslint/lib/rules/no-labels.js +++ b/eslint/lib/rules/no-labels.js @@ -20,7 +20,6 @@ module.exports = { docs: { description: "disallow labeled statements", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-labels" }, diff --git a/eslint/lib/rules/no-lone-blocks.js b/eslint/lib/rules/no-lone-blocks.js index 290784b..33d4706 100644 --- a/eslint/lib/rules/no-lone-blocks.js +++ b/eslint/lib/rules/no-lone-blocks.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "disallow unnecessary nested blocks", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-lone-blocks" }, @@ -40,7 +39,9 @@ module.exports = { * @returns {void} */ function report(node) { - const messageId = node.parent.type === "BlockStatement" ? "redundantNestedBlock" : "redundantBlock"; + const messageId = node.parent.type === "BlockStatement" || node.parent.type === "StaticBlock" + ? "redundantNestedBlock" + : "redundantBlock"; context.report({ node, @@ -55,6 +56,7 @@ module.exports = { */ function isLoneBlock(node) { return node.parent.type === "BlockStatement" || + node.parent.type === "StaticBlock" || node.parent.type === "Program" || // Don't report blocks in switch cases if the block is the only statement of the case. @@ -100,7 +102,10 @@ module.exports = { loneBlocks.pop(); report(node); } else if ( - node.parent.type === "BlockStatement" && + ( + node.parent.type === "BlockStatement" || + node.parent.type === "StaticBlock" + ) && node.parent.body.length === 1 ) { report(node); diff --git a/eslint/lib/rules/no-lonely-if.js b/eslint/lib/rules/no-lonely-if.js index 6552adc..e44f000 100644 --- a/eslint/lib/rules/no-lonely-if.js +++ b/eslint/lib/rules/no-lonely-if.js @@ -14,7 +14,6 @@ module.exports = { docs: { description: "disallow `if` statements as the only statement in `else` blocks", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/no-lonely-if" }, diff --git a/eslint/lib/rules/no-loop-func.js b/eslint/lib/rules/no-loop-func.js index 13ebd3e..d1a7868 100644 --- a/eslint/lib/rules/no-loop-func.js +++ b/eslint/lib/rules/no-loop-func.js @@ -154,7 +154,6 @@ module.exports = { docs: { description: "disallow function declarations that contain unsafe references inside loop statements", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-loop-func" }, @@ -174,7 +173,7 @@ module.exports = { * - has a loop node in ancestors. * - has any references which refers to an unsafe variable. * @param {ASTNode} node The AST node to check. - * @returns {boolean} Whether or not the node is within a loop. + * @returns {void} */ function checkForLoops(node) { const loopNode = getContainingLoopNode(node); diff --git a/eslint/lib/rules/no-loss-of-precision.js b/eslint/lib/rules/no-loss-of-precision.js index 2d0c618..417616d 100644 --- a/eslint/lib/rules/no-loss-of-precision.js +++ b/eslint/lib/rules/no-loss-of-precision.js @@ -15,8 +15,7 @@ module.exports = { docs: { description: "disallow literal numbers that lose precision", - category: "Possible Errors", - recommended: false, + recommended: true, url: "https://eslint.org/docs/rules/no-loss-of-precision" }, schema: [], diff --git a/eslint/lib/rules/no-magic-numbers.js b/eslint/lib/rules/no-magic-numbers.js index 510b3f9..a2c678e 100644 --- a/eslint/lib/rules/no-magic-numbers.js +++ b/eslint/lib/rules/no-magic-numbers.js @@ -32,7 +32,6 @@ module.exports = { docs: { description: "disallow magic numbers", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-magic-numbers" }, diff --git a/eslint/lib/rules/no-misleading-character-class.js b/eslint/lib/rules/no-misleading-character-class.js index 3d00461..70e31e6 100644 --- a/eslint/lib/rules/no-misleading-character-class.js +++ b/eslint/lib/rules/no-misleading-character-class.js @@ -104,7 +104,6 @@ module.exports = { docs: { description: "disallow characters which are made with multiple code points in character class syntax", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-misleading-character-class" }, diff --git a/eslint/lib/rules/no-mixed-operators.js b/eslint/lib/rules/no-mixed-operators.js index 15eb20b..ed37a90 100644 --- a/eslint/lib/rules/no-mixed-operators.js +++ b/eslint/lib/rules/no-mixed-operators.js @@ -58,7 +58,7 @@ function normalizeOptions(options = {}) { /** * Checks whether any group which includes both given operator exists or not. - * @param {Array.} groups A list of groups to check. + * @param {Array} groups A list of groups to check. * @param {string} left An operator. * @param {string} right Another operator. * @returns {boolean} `true` if such group existed. @@ -88,7 +88,6 @@ module.exports = { docs: { description: "disallow mixed binary operators", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/no-mixed-operators" }, @@ -117,7 +116,7 @@ module.exports = { ], messages: { - unexpectedMixedOperator: "Unexpected mix of '{{leftOperator}}' and '{{rightOperator}}'." + unexpectedMixedOperator: "Unexpected mix of '{{leftOperator}}' and '{{rightOperator}}'. Use parentheses to clarify the intended order of operations." } }, diff --git a/eslint/lib/rules/no-mixed-requires.js b/eslint/lib/rules/no-mixed-requires.js index a02de91..f7c2d11 100644 --- a/eslint/lib/rules/no-mixed-requires.js +++ b/eslint/lib/rules/no-mixed-requires.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to enforce grouped require statements for Node.JS * @author Raphael Pigulla + * @deprecated in ESLint v7.0.0 */ "use strict"; @@ -19,7 +20,6 @@ module.exports = { docs: { description: "disallow `require` calls to be mixed with regular variable declarations", - category: "Node.js and CommonJS", recommended: false, url: "https://eslint.org/docs/rules/no-mixed-requires" }, diff --git a/eslint/lib/rules/no-mixed-spaces-and-tabs.js b/eslint/lib/rules/no-mixed-spaces-and-tabs.js index 287cbda..ac73cdd 100644 --- a/eslint/lib/rules/no-mixed-spaces-and-tabs.js +++ b/eslint/lib/rules/no-mixed-spaces-and-tabs.js @@ -14,7 +14,6 @@ module.exports = { docs: { description: "disallow mixed spaces and tabs for indentation", - category: "Stylistic Issues", recommended: true, url: "https://eslint.org/docs/rules/no-mixed-spaces-and-tabs" }, diff --git a/eslint/lib/rules/no-multi-assign.js b/eslint/lib/rules/no-multi-assign.js index d2606a1..8d7bd32 100644 --- a/eslint/lib/rules/no-multi-assign.js +++ b/eslint/lib/rules/no-multi-assign.js @@ -16,7 +16,6 @@ module.exports = { docs: { description: "disallow use of chained assignment expressions", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/no-multi-assign" }, @@ -45,16 +44,21 @@ module.exports = { const options = context.options[0] || { ignoreNonDeclaration: false }; - const targetParent = options.ignoreNonDeclaration ? ["VariableDeclarator"] : ["AssignmentExpression", "VariableDeclarator"]; + const selectors = [ + "VariableDeclarator > AssignmentExpression.init", + "PropertyDefinition > AssignmentExpression.value" + ]; + + if (!options.ignoreNonDeclaration) { + selectors.push("AssignmentExpression > AssignmentExpression.right"); + } return { - AssignmentExpression(node) { - if (targetParent.indexOf(node.parent.type) !== -1) { - context.report({ - node, - messageId: "unexpectedChain" - }); - } + [selectors](node) { + context.report({ + node, + messageId: "unexpectedChain" + }); } }; diff --git a/eslint/lib/rules/no-multi-spaces.js b/eslint/lib/rules/no-multi-spaces.js index d43ed73..0134dd2 100644 --- a/eslint/lib/rules/no-multi-spaces.js +++ b/eslint/lib/rules/no-multi-spaces.js @@ -17,7 +17,6 @@ module.exports = { docs: { description: "disallow multiple spaces", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-multi-spaces" }, diff --git a/eslint/lib/rules/no-multi-str.js b/eslint/lib/rules/no-multi-str.js index 7cf1ae3..848f8d4 100644 --- a/eslint/lib/rules/no-multi-str.js +++ b/eslint/lib/rules/no-multi-str.js @@ -21,7 +21,6 @@ module.exports = { docs: { description: "disallow multiline strings", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-multi-str" }, diff --git a/eslint/lib/rules/no-multiple-empty-lines.js b/eslint/lib/rules/no-multiple-empty-lines.js index 9cccef3..33ac76f 100644 --- a/eslint/lib/rules/no-multiple-empty-lines.js +++ b/eslint/lib/rules/no-multiple-empty-lines.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "disallow multiple empty lines", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/no-multiple-empty-lines" }, diff --git a/eslint/lib/rules/no-native-reassign.js b/eslint/lib/rules/no-native-reassign.js index 833e3b7..80ba094 100644 --- a/eslint/lib/rules/no-native-reassign.js +++ b/eslint/lib/rules/no-native-reassign.js @@ -16,7 +16,6 @@ module.exports = { docs: { description: "disallow assignments to native objects or read-only global variables", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-native-reassign" }, diff --git a/eslint/lib/rules/no-negated-condition.js b/eslint/lib/rules/no-negated-condition.js index 8a9eba8..b5cbadc 100644 --- a/eslint/lib/rules/no-negated-condition.js +++ b/eslint/lib/rules/no-negated-condition.js @@ -14,7 +14,6 @@ module.exports = { docs: { description: "disallow negated conditions", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/no-negated-condition" }, diff --git a/eslint/lib/rules/no-negated-in-lhs.js b/eslint/lib/rules/no-negated-in-lhs.js index 1229ced..0f9c84b 100644 --- a/eslint/lib/rules/no-negated-in-lhs.js +++ b/eslint/lib/rules/no-negated-in-lhs.js @@ -16,7 +16,6 @@ module.exports = { docs: { description: "disallow negating the left operand in `in` expressions", - category: "Possible Errors", recommended: false, url: "https://eslint.org/docs/rules/no-negated-in-lhs" }, diff --git a/eslint/lib/rules/no-nested-ternary.js b/eslint/lib/rules/no-nested-ternary.js index 383bb23..2d3359d 100644 --- a/eslint/lib/rules/no-nested-ternary.js +++ b/eslint/lib/rules/no-nested-ternary.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "disallow nested ternary expressions", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/no-nested-ternary" }, diff --git a/eslint/lib/rules/no-new-func.js b/eslint/lib/rules/no-new-func.js index 9af4e31..ddf6102 100644 --- a/eslint/lib/rules/no-new-func.js +++ b/eslint/lib/rules/no-new-func.js @@ -5,6 +5,18 @@ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const callMethods = new Set(["apply", "bind", "call"]); + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -15,7 +27,6 @@ module.exports = { docs: { description: "disallow `new` operators with the `Function` object", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-new-func" }, @@ -38,14 +49,30 @@ module.exports = { variable.references.forEach(ref => { const node = ref.identifier; const { parent } = node; + let evalNode; + + if (parent) { + if (node === parent.callee && ( + parent.type === "NewExpression" || + parent.type === "CallExpression" + )) { + evalNode = parent; + } else if ( + parent.type === "MemberExpression" && + node === parent.object && + callMethods.has(astUtils.getStaticPropertyName(parent)) + ) { + const maybeCallee = parent.parent.type === "ChainExpression" ? parent.parent : parent; + + if (maybeCallee.parent.type === "CallExpression" && maybeCallee.parent.callee === maybeCallee) { + evalNode = maybeCallee.parent; + } + } + } - if ( - parent && - (parent.type === "NewExpression" || parent.type === "CallExpression") && - node === parent.callee - ) { + if (evalNode) { context.report({ - node: parent, + node: evalNode, messageId: "noFunctionConstructor" }); } diff --git a/eslint/lib/rules/no-new-object.js b/eslint/lib/rules/no-new-object.js index e9f915d..17dfd34 100644 --- a/eslint/lib/rules/no-new-object.js +++ b/eslint/lib/rules/no-new-object.js @@ -21,7 +21,6 @@ module.exports = { docs: { description: "disallow `Object` constructors", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/no-new-object" }, diff --git a/eslint/lib/rules/no-new-require.js b/eslint/lib/rules/no-new-require.js index 063f783..7973f8f 100644 --- a/eslint/lib/rules/no-new-require.js +++ b/eslint/lib/rules/no-new-require.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to disallow use of new operator with the `require` function * @author Wil Moore III + * @deprecated in ESLint v7.0.0 */ "use strict"; @@ -19,7 +20,6 @@ module.exports = { docs: { description: "disallow `new` operators with calls to `require`", - category: "Node.js and CommonJS", recommended: false, url: "https://eslint.org/docs/rules/no-new-require" }, diff --git a/eslint/lib/rules/no-new-symbol.js b/eslint/lib/rules/no-new-symbol.js index aeb509c..391527d 100644 --- a/eslint/lib/rules/no-new-symbol.js +++ b/eslint/lib/rules/no-new-symbol.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "disallow `new` operators with the `Symbol` object", - category: "ECMAScript 6", recommended: true, url: "https://eslint.org/docs/rules/no-new-symbol" }, diff --git a/eslint/lib/rules/no-new-wrappers.js b/eslint/lib/rules/no-new-wrappers.js index d276c48..b697d8d 100644 --- a/eslint/lib/rules/no-new-wrappers.js +++ b/eslint/lib/rules/no-new-wrappers.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "disallow `new` operators with the `String`, `Number`, and `Boolean` objects", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-new-wrappers" }, diff --git a/eslint/lib/rules/no-new.js b/eslint/lib/rules/no-new.js index aa8a4e2..1b37f07 100644 --- a/eslint/lib/rules/no-new.js +++ b/eslint/lib/rules/no-new.js @@ -16,7 +16,6 @@ module.exports = { docs: { description: "disallow `new` operators outside of assignments or comparisons", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-new" }, diff --git a/eslint/lib/rules/no-nonoctal-decimal-escape.js b/eslint/lib/rules/no-nonoctal-decimal-escape.js index a4b46d9..da61f61 100644 --- a/eslint/lib/rules/no-nonoctal-decimal-escape.js +++ b/eslint/lib/rules/no-nonoctal-decimal-escape.js @@ -30,12 +30,12 @@ module.exports = { docs: { description: "disallow `\\8` and `\\9` escape sequences in string literals", - category: "Best Practices", - recommended: false, - url: "https://eslint.org/docs/rules/no-nonoctal-decimal-escape", - suggestion: true + recommended: true, + url: "https://eslint.org/docs/rules/no-nonoctal-decimal-escape" }, + hasSuggestions: true, + schema: [], messages: { diff --git a/eslint/lib/rules/no-obj-calls.js b/eslint/lib/rules/no-obj-calls.js index 6eb200c..d62c1f0 100644 --- a/eslint/lib/rules/no-obj-calls.js +++ b/eslint/lib/rules/no-obj-calls.js @@ -43,7 +43,6 @@ module.exports = { docs: { description: "disallow calling global object properties as functions", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-obj-calls" }, diff --git a/eslint/lib/rules/no-octal-escape.js b/eslint/lib/rules/no-octal-escape.js index 5b4c7b2..4513a83 100644 --- a/eslint/lib/rules/no-octal-escape.js +++ b/eslint/lib/rules/no-octal-escape.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "disallow octal escape sequences in string literals", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-octal-escape" }, diff --git a/eslint/lib/rules/no-octal.js b/eslint/lib/rules/no-octal.js index e9940be..5ee6895 100644 --- a/eslint/lib/rules/no-octal.js +++ b/eslint/lib/rules/no-octal.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "disallow octal literals", - category: "Best Practices", recommended: true, url: "https://eslint.org/docs/rules/no-octal" }, diff --git a/eslint/lib/rules/no-param-reassign.js b/eslint/lib/rules/no-param-reassign.js index 6874af4..b758b9d 100644 --- a/eslint/lib/rules/no-param-reassign.js +++ b/eslint/lib/rules/no-param-reassign.js @@ -16,7 +16,6 @@ module.exports = { docs: { description: "disallow reassigning `function` parameters", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-param-reassign" }, diff --git a/eslint/lib/rules/no-path-concat.js b/eslint/lib/rules/no-path-concat.js index fc1f894..184c918 100644 --- a/eslint/lib/rules/no-path-concat.js +++ b/eslint/lib/rules/no-path-concat.js @@ -1,6 +1,7 @@ /** * @fileoverview Disallow string concatenation when using __dirname and __filename * @author Nicholas C. Zakas + * @deprecated in ESLint v7.0.0 */ "use strict"; @@ -18,7 +19,6 @@ module.exports = { docs: { description: "disallow string concatenation with `__dirname` and `__filename`", - category: "Node.js and CommonJS", recommended: false, url: "https://eslint.org/docs/rules/no-path-concat" }, diff --git a/eslint/lib/rules/no-plusplus.js b/eslint/lib/rules/no-plusplus.js index 84d6c3e..d7b6c73 100644 --- a/eslint/lib/rules/no-plusplus.js +++ b/eslint/lib/rules/no-plusplus.js @@ -51,7 +51,6 @@ module.exports = { docs: { description: "disallow the unary operators `++` and `--`", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/no-plusplus" }, diff --git a/eslint/lib/rules/no-process-env.js b/eslint/lib/rules/no-process-env.js index 49d1734..c61b557 100644 --- a/eslint/lib/rules/no-process-env.js +++ b/eslint/lib/rules/no-process-env.js @@ -1,6 +1,7 @@ /** * @fileoverview Disallow the use of process.env() * @author Vignesh Anand + * @deprecated in ESLint v7.0.0 */ "use strict"; @@ -18,7 +19,6 @@ module.exports = { docs: { description: "disallow the use of `process.env`", - category: "Node.js and CommonJS", recommended: false, url: "https://eslint.org/docs/rules/no-process-env" }, diff --git a/eslint/lib/rules/no-process-exit.js b/eslint/lib/rules/no-process-exit.js index 77c9cfd..73310a9 100644 --- a/eslint/lib/rules/no-process-exit.js +++ b/eslint/lib/rules/no-process-exit.js @@ -1,6 +1,7 @@ /** * @fileoverview Disallow the use of process.exit() * @author Nicholas C. Zakas + * @deprecated in ESLint v7.0.0 */ "use strict"; @@ -18,7 +19,6 @@ module.exports = { docs: { description: "disallow the use of `process.exit()`", - category: "Node.js and CommonJS", recommended: false, url: "https://eslint.org/docs/rules/no-process-exit" }, diff --git a/eslint/lib/rules/no-promise-executor-return.js b/eslint/lib/rules/no-promise-executor-return.js index 32ee6e1..4265241 100644 --- a/eslint/lib/rules/no-promise-executor-return.js +++ b/eslint/lib/rules/no-promise-executor-return.js @@ -69,7 +69,6 @@ module.exports = { docs: { description: "disallow returning values from Promise executor functions", - category: "Possible Errors", recommended: false, url: "https://eslint.org/docs/rules/no-promise-executor-return" }, diff --git a/eslint/lib/rules/no-proto.js b/eslint/lib/rules/no-proto.js index 82ce02f..0c2490f 100644 --- a/eslint/lib/rules/no-proto.js +++ b/eslint/lib/rules/no-proto.js @@ -21,7 +21,6 @@ module.exports = { docs: { description: "disallow the use of the `__proto__` property", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-proto" }, diff --git a/eslint/lib/rules/no-prototype-builtins.js b/eslint/lib/rules/no-prototype-builtins.js index c5e4d49..1f837b9 100644 --- a/eslint/lib/rules/no-prototype-builtins.js +++ b/eslint/lib/rules/no-prototype-builtins.js @@ -20,7 +20,6 @@ module.exports = { docs: { description: "disallow calling some `Object.prototype` methods directly on objects", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-prototype-builtins" }, diff --git a/eslint/lib/rules/no-redeclare.js b/eslint/lib/rules/no-redeclare.js index 6ddb21c..3de4397 100644 --- a/eslint/lib/rules/no-redeclare.js +++ b/eslint/lib/rules/no-redeclare.js @@ -21,7 +21,6 @@ module.exports = { docs: { description: "disallow variable redeclaration", - category: "Best Practices", recommended: true, url: "https://eslint.org/docs/rules/no-redeclare" }, @@ -162,6 +161,8 @@ module.exports = { FunctionExpression: checkForBlock, ArrowFunctionExpression: checkForBlock, + StaticBlock: checkForBlock, + BlockStatement: checkForBlock, ForStatement: checkForBlock, ForInStatement: checkForBlock, diff --git a/eslint/lib/rules/no-regex-spaces.js b/eslint/lib/rules/no-regex-spaces.js index e6d4c9e..1d6b121 100644 --- a/eslint/lib/rules/no-regex-spaces.js +++ b/eslint/lib/rules/no-regex-spaces.js @@ -39,7 +39,6 @@ module.exports = { docs: { description: "disallow multiple spaces in regular expressions", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-regex-spaces" }, diff --git a/eslint/lib/rules/no-restricted-exports.js b/eslint/lib/rules/no-restricted-exports.js index f0df0ff..f568fdc 100644 --- a/eslint/lib/rules/no-restricted-exports.js +++ b/eslint/lib/rules/no-restricted-exports.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "disallow specified names in exports", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/no-restricted-exports" }, diff --git a/eslint/lib/rules/no-restricted-globals.js b/eslint/lib/rules/no-restricted-globals.js index 2c932a7..efbcd75 100644 --- a/eslint/lib/rules/no-restricted-globals.js +++ b/eslint/lib/rules/no-restricted-globals.js @@ -14,7 +14,6 @@ module.exports = { docs: { description: "disallow specified global variables", - category: "Variables", recommended: false, url: "https://eslint.org/docs/rules/no-restricted-globals" }, @@ -43,7 +42,7 @@ module.exports = { messages: { defaultMessage: "Unexpected use of '{{name}}'.", - // eslint-disable-next-line eslint-plugin/report-message-format + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period customMessage: "Unexpected use of '{{name}}'. {{customMessage}}" } }, diff --git a/eslint/lib/rules/no-restricted-imports.js b/eslint/lib/rules/no-restricted-imports.js index 414164d..eda6340 100644 --- a/eslint/lib/rules/no-restricted-imports.js +++ b/eslint/lib/rules/no-restricted-imports.js @@ -79,26 +79,25 @@ module.exports = { docs: { description: "disallow specified modules when loaded by `import`", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/no-restricted-imports" }, messages: { path: "'{{importSource}}' import is restricted from being used.", - // eslint-disable-next-line eslint-plugin/report-message-format + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period pathWithCustomMessage: "'{{importSource}}' import is restricted from being used. {{customMessage}}", patterns: "'{{importSource}}' import is restricted from being used by a pattern.", - // eslint-disable-next-line eslint-plugin/report-message-format + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period patternWithCustomMessage: "'{{importSource}}' import is restricted from being used by a pattern. {{customMessage}}", everything: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted.", - // eslint-disable-next-line eslint-plugin/report-message-format + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period everythingWithCustomMessage: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted. {{customMessage}}", importName: "'{{importName}}' import from '{{importSource}}' is restricted.", - // eslint-disable-next-line eslint-plugin/report-message-format + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period importNameWithCustomMessage: "'{{importName}}' import from '{{importSource}}' is restricted. {{customMessage}}" }, @@ -147,7 +146,7 @@ module.exports = { ? [{ matcher: ignore().add(restrictedPatterns) }] : restrictedPatterns.map(({ group, message }) => ({ matcher: ignore().add(group), customMessage: message })); - // if no imports are restricted we don"t need to check + // if no imports are restricted we don't need to check if (Object.keys(restrictedPaths).length === 0 && restrictedPatternGroups.length === 0) { return {}; } diff --git a/eslint/lib/rules/no-restricted-modules.js b/eslint/lib/rules/no-restricted-modules.js index d0b8a78..66e6fe4 100644 --- a/eslint/lib/rules/no-restricted-modules.js +++ b/eslint/lib/rules/no-restricted-modules.js @@ -1,6 +1,7 @@ /** * @fileoverview Restrict usage of specified node modules. * @author Christian Schulz + * @deprecated in ESLint v7.0.0 */ "use strict"; @@ -48,7 +49,6 @@ module.exports = { docs: { description: "disallow specified modules when loaded by `require`", - category: "Node.js and CommonJS", recommended: false, url: "https://eslint.org/docs/rules/no-restricted-modules" }, @@ -73,7 +73,7 @@ module.exports = { messages: { defaultMessage: "'{{name}}' module is restricted from being used.", - // eslint-disable-next-line eslint-plugin/report-message-format + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period customMessage: "'{{name}}' module is restricted from being used. {{customMessage}}", patternMessage: "'{{name}}' module is restricted from being used by a pattern." } @@ -97,7 +97,7 @@ module.exports = { return memo; }, {}); - // if no imports are restricted we don"t need to check + // if no imports are restricted we don't need to check if (Object.keys(restrictedPaths).length === 0 && restrictedPatterns.length === 0) { return {}; } diff --git a/eslint/lib/rules/no-restricted-properties.js b/eslint/lib/rules/no-restricted-properties.js index 7ab8399..3671d88 100644 --- a/eslint/lib/rules/no-restricted-properties.js +++ b/eslint/lib/rules/no-restricted-properties.js @@ -17,7 +17,6 @@ module.exports = { docs: { description: "disallow certain properties on certain objects", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-restricted-properties" }, @@ -64,9 +63,9 @@ module.exports = { }, messages: { - // eslint-disable-next-line eslint-plugin/report-message-format + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period restrictedObjectProperty: "'{{objectName}}.{{propertyName}}' is restricted from being used.{{message}}", - // eslint-disable-next-line eslint-plugin/report-message-format + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period restrictedProperty: "'{{propertyName}}' is restricted from being used.{{message}}" } }, diff --git a/eslint/lib/rules/no-restricted-syntax.js b/eslint/lib/rules/no-restricted-syntax.js index 9572603..0ff6b91 100644 --- a/eslint/lib/rules/no-restricted-syntax.js +++ b/eslint/lib/rules/no-restricted-syntax.js @@ -14,7 +14,6 @@ module.exports = { docs: { description: "disallow specified syntax", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/no-restricted-syntax" }, @@ -42,7 +41,7 @@ module.exports = { }, messages: { - // eslint-disable-next-line eslint-plugin/report-message-format + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period restrictedSyntax: "{{message}}" } }, diff --git a/eslint/lib/rules/no-return-assign.js b/eslint/lib/rules/no-return-assign.js index 4b57d42..ecb789e 100644 --- a/eslint/lib/rules/no-return-assign.js +++ b/eslint/lib/rules/no-return-assign.js @@ -26,7 +26,6 @@ module.exports = { docs: { description: "disallow assignment operators in `return` statements", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-return-assign" }, diff --git a/eslint/lib/rules/no-return-await.js b/eslint/lib/rules/no-return-await.js index d1d8982..7ec808f 100644 --- a/eslint/lib/rules/no-return-await.js +++ b/eslint/lib/rules/no-return-await.js @@ -16,7 +16,6 @@ module.exports = { docs: { description: "disallow unnecessary `return await`", - category: "Best Practices", recommended: false, diff --git a/eslint/lib/rules/no-script-url.js b/eslint/lib/rules/no-script-url.js index 0c82052..12451ad 100644 --- a/eslint/lib/rules/no-script-url.js +++ b/eslint/lib/rules/no-script-url.js @@ -2,8 +2,7 @@ * @fileoverview Rule to flag when using javascript: urls * @author Ilya Volodin */ -/* jshint scripturl: true */ -/* eslint no-script-url: 0 */ +/* eslint no-script-url: 0 -- Code is checking to report such URLs */ "use strict"; @@ -19,7 +18,6 @@ module.exports = { docs: { description: "disallow `javascript:` urls", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-script-url" }, diff --git a/eslint/lib/rules/no-self-assign.js b/eslint/lib/rules/no-self-assign.js index 705be32..813771e 100644 --- a/eslint/lib/rules/no-self-assign.js +++ b/eslint/lib/rules/no-self-assign.js @@ -130,7 +130,6 @@ module.exports = { docs: { description: "disallow assignments where both sides are exactly the same", - category: "Best Practices", recommended: true, url: "https://eslint.org/docs/rules/no-self-assign" }, diff --git a/eslint/lib/rules/no-self-compare.js b/eslint/lib/rules/no-self-compare.js index 79b6ac7..ee77ff0 100644 --- a/eslint/lib/rules/no-self-compare.js +++ b/eslint/lib/rules/no-self-compare.js @@ -16,7 +16,6 @@ module.exports = { docs: { description: "disallow comparisons where both sides are exactly the same", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-self-compare" }, diff --git a/eslint/lib/rules/no-sequences.js b/eslint/lib/rules/no-sequences.js index fe51697..b894125 100644 --- a/eslint/lib/rules/no-sequences.js +++ b/eslint/lib/rules/no-sequences.js @@ -29,7 +29,6 @@ module.exports = { docs: { description: "disallow comma operators", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-sequences" }, diff --git a/eslint/lib/rules/no-setter-return.js b/eslint/lib/rules/no-setter-return.js index 9c79240..67114ad 100644 --- a/eslint/lib/rules/no-setter-return.js +++ b/eslint/lib/rules/no-setter-return.js @@ -93,6 +93,7 @@ function isSetter(node, scope) { const parent = node.parent; if ( + (parent.type === "Property" || parent.type === "MethodDefinition") && parent.kind === "set" && parent.value === node ) { @@ -141,7 +142,6 @@ module.exports = { docs: { description: "disallow returning values from setters", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-setter-return" }, diff --git a/eslint/lib/rules/no-shadow-restricted-names.js b/eslint/lib/rules/no-shadow-restricted-names.js index 9647e9a..7d4174a 100644 --- a/eslint/lib/rules/no-shadow-restricted-names.js +++ b/eslint/lib/rules/no-shadow-restricted-names.js @@ -27,7 +27,6 @@ module.exports = { docs: { description: "disallow identifiers from shadowing restricted names", - category: "Variables", recommended: true, url: "https://eslint.org/docs/rules/no-shadow-restricted-names" }, diff --git a/eslint/lib/rules/no-shadow.js b/eslint/lib/rules/no-shadow.js index a0b1db5..4ec3576 100644 --- a/eslint/lib/rules/no-shadow.js +++ b/eslint/lib/rules/no-shadow.js @@ -21,7 +21,6 @@ module.exports = { docs: { description: "disallow variable declarations from shadowing variables declared in the outer scope", - category: "Variables", recommended: false, url: "https://eslint.org/docs/rules/no-shadow" }, @@ -59,7 +58,7 @@ module.exports = { /** * Check if variable name is allowed. - * @param {ASTNode} variable The variable to check. + * @param {ASTNode} variable The variable to check. * @returns {boolean} Whether or not the variable name is allowed. */ function isAllowed(variable) { diff --git a/eslint/lib/rules/no-spaced-func.js b/eslint/lib/rules/no-spaced-func.js index 961bc68..8f51d54 100644 --- a/eslint/lib/rules/no-spaced-func.js +++ b/eslint/lib/rules/no-spaced-func.js @@ -16,7 +16,6 @@ module.exports = { docs: { description: "disallow spacing between function identifiers and their applications (deprecated)", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/no-spaced-func" }, diff --git a/eslint/lib/rules/no-sparse-arrays.js b/eslint/lib/rules/no-sparse-arrays.js index e8407c3..56ce5dc 100644 --- a/eslint/lib/rules/no-sparse-arrays.js +++ b/eslint/lib/rules/no-sparse-arrays.js @@ -14,7 +14,6 @@ module.exports = { docs: { description: "disallow sparse arrays", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-sparse-arrays" }, diff --git a/eslint/lib/rules/no-sync.js b/eslint/lib/rules/no-sync.js index 06424e0..ea40df1 100644 --- a/eslint/lib/rules/no-sync.js +++ b/eslint/lib/rules/no-sync.js @@ -1,10 +1,9 @@ /** * @fileoverview Rule to check for properties whose identifier ends with the string Sync * @author Matt DuVall + * @deprecated in ESLint v7.0.0 */ -/* jshint node:true */ - "use strict"; //------------------------------------------------------------------------------ @@ -21,7 +20,6 @@ module.exports = { docs: { description: "disallow synchronous methods", - category: "Node.js and CommonJS", recommended: false, url: "https://eslint.org/docs/rules/no-sync" }, diff --git a/eslint/lib/rules/no-tabs.js b/eslint/lib/rules/no-tabs.js index ca7be26..1f3921a 100644 --- a/eslint/lib/rules/no-tabs.js +++ b/eslint/lib/rules/no-tabs.js @@ -22,7 +22,6 @@ module.exports = { docs: { description: "disallow all tabs", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/no-tabs" }, diff --git a/eslint/lib/rules/no-template-curly-in-string.js b/eslint/lib/rules/no-template-curly-in-string.js index 539cd5b..e71480f 100644 --- a/eslint/lib/rules/no-template-curly-in-string.js +++ b/eslint/lib/rules/no-template-curly-in-string.js @@ -14,7 +14,6 @@ module.exports = { docs: { description: "disallow template literal placeholder syntax in regular strings", - category: "Possible Errors", recommended: false, url: "https://eslint.org/docs/rules/no-template-curly-in-string" }, diff --git a/eslint/lib/rules/no-ternary.js b/eslint/lib/rules/no-ternary.js index b3ced86..8b2e10a 100644 --- a/eslint/lib/rules/no-ternary.js +++ b/eslint/lib/rules/no-ternary.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "disallow ternary operators", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/no-ternary" }, diff --git a/eslint/lib/rules/no-this-before-super.js b/eslint/lib/rules/no-this-before-super.js index 5bfba66..9cc85eb 100644 --- a/eslint/lib/rules/no-this-before-super.js +++ b/eslint/lib/rules/no-this-before-super.js @@ -40,7 +40,6 @@ module.exports = { docs: { description: "disallow `this`/`super` before calling `super()` in constructors", - category: "ECMAScript 6", recommended: true, url: "https://eslint.org/docs/rules/no-this-before-super" }, diff --git a/eslint/lib/rules/no-throw-literal.js b/eslint/lib/rules/no-throw-literal.js index 29fb371..311e6d4 100644 --- a/eslint/lib/rules/no-throw-literal.js +++ b/eslint/lib/rules/no-throw-literal.js @@ -17,7 +17,6 @@ module.exports = { docs: { description: "disallow throwing literals as exceptions", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-throw-literal" }, diff --git a/eslint/lib/rules/no-trailing-spaces.js b/eslint/lib/rules/no-trailing-spaces.js index 98ae62c..1930098 100644 --- a/eslint/lib/rules/no-trailing-spaces.js +++ b/eslint/lib/rules/no-trailing-spaces.js @@ -20,7 +20,6 @@ module.exports = { docs: { description: "disallow trailing whitespace at the end of lines", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/no-trailing-spaces" }, diff --git a/eslint/lib/rules/no-undef-init.js b/eslint/lib/rules/no-undef-init.js index 5c240fe..7298d34 100644 --- a/eslint/lib/rules/no-undef-init.js +++ b/eslint/lib/rules/no-undef-init.js @@ -17,7 +17,6 @@ module.exports = { docs: { description: "disallow initializing variables to `undefined`", - category: "Variables", recommended: false, url: "https://eslint.org/docs/rules/no-undef-init" }, diff --git a/eslint/lib/rules/no-undef.js b/eslint/lib/rules/no-undef.js index 6b51408..ee611f9 100644 --- a/eslint/lib/rules/no-undef.js +++ b/eslint/lib/rules/no-undef.js @@ -29,7 +29,6 @@ module.exports = { docs: { description: "disallow the use of undeclared variables unless mentioned in `/*global */` comments", - category: "Variables", recommended: true, url: "https://eslint.org/docs/rules/no-undef" }, diff --git a/eslint/lib/rules/no-undefined.js b/eslint/lib/rules/no-undefined.js index a075d90..ad30225 100644 --- a/eslint/lib/rules/no-undefined.js +++ b/eslint/lib/rules/no-undefined.js @@ -14,7 +14,6 @@ module.exports = { docs: { description: "disallow the use of `undefined` as an identifier", - category: "Variables", recommended: false, url: "https://eslint.org/docs/rules/no-undefined" }, diff --git a/eslint/lib/rules/no-underscore-dangle.js b/eslint/lib/rules/no-underscore-dangle.js index 87d2336..916b8c0 100644 --- a/eslint/lib/rules/no-underscore-dangle.js +++ b/eslint/lib/rules/no-underscore-dangle.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "disallow dangling underscores in identifiers", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/no-underscore-dangle" }, @@ -253,7 +252,9 @@ module.exports = { node, messageId: "unexpectedUnderscore", data: { - identifier + identifier: node.key.type === "PrivateIdentifier" + ? `#${identifier}` + : identifier } }); } @@ -268,6 +269,7 @@ module.exports = { VariableDeclarator: checkForDanglingUnderscoreInVariableExpression, MemberExpression: checkForDanglingUnderscoreInMemberExpression, MethodDefinition: checkForDanglingUnderscoreInMethod, + PropertyDefinition: checkForDanglingUnderscoreInMethod, Property: checkForDanglingUnderscoreInMethod, FunctionExpression: checkForDanglingUnderscoreInFunction, ArrowFunctionExpression: checkForDanglingUnderscoreInFunction diff --git a/eslint/lib/rules/no-unexpected-multiline.js b/eslint/lib/rules/no-unexpected-multiline.js index 7af3fe6..4447959 100644 --- a/eslint/lib/rules/no-unexpected-multiline.js +++ b/eslint/lib/rules/no-unexpected-multiline.js @@ -20,7 +20,6 @@ module.exports = { docs: { description: "disallow confusing multiline expressions", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-unexpected-multiline" }, diff --git a/eslint/lib/rules/no-unmodified-loop-condition.js b/eslint/lib/rules/no-unmodified-loop-condition.js index 7031a4d..ba321d2 100644 --- a/eslint/lib/rules/no-unmodified-loop-condition.js +++ b/eslint/lib/rules/no-unmodified-loop-condition.js @@ -162,7 +162,6 @@ module.exports = { docs: { description: "disallow unmodified loop conditions", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-unmodified-loop-condition" }, diff --git a/eslint/lib/rules/no-unneeded-ternary.js b/eslint/lib/rules/no-unneeded-ternary.js index 06c615f..e12240d 100644 --- a/eslint/lib/rules/no-unneeded-ternary.js +++ b/eslint/lib/rules/no-unneeded-ternary.js @@ -29,7 +29,6 @@ module.exports = { docs: { description: "disallow ternary operators when simpler alternatives exist", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/no-unneeded-ternary" }, diff --git a/eslint/lib/rules/no-unreachable-loop.js b/eslint/lib/rules/no-unreachable-loop.js index 868a6ff..5cbfac4 100644 --- a/eslint/lib/rules/no-unreachable-loop.js +++ b/eslint/lib/rules/no-unreachable-loop.js @@ -59,7 +59,6 @@ module.exports = { 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" }, diff --git a/eslint/lib/rules/no-unreachable.js b/eslint/lib/rules/no-unreachable.js index 415631a..ce17a59 100644 --- a/eslint/lib/rules/no-unreachable.js +++ b/eslint/lib/rules/no-unreachable.js @@ -8,6 +8,12 @@ // Helpers //------------------------------------------------------------------------------ +/** + * @typedef {Object} ConstructorInfo + * @property {ConstructorInfo | null} upper Info about the constructor that encloses this constructor. + * @property {boolean} hasSuperCall The flag about having `super()` expressions. + */ + /** * Checks whether or not a given variable declarator has the initializer. * @param {ASTNode} node A VariableDeclarator node to check. @@ -105,7 +111,6 @@ module.exports = { docs: { description: "disallow unreachable code after `return`, `throw`, `continue`, and `break` statements", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-unreachable" }, @@ -120,6 +125,10 @@ module.exports = { create(context) { let currentCodePath = null; + /** @type {ConstructorInfo | null} */ + let constructorInfo = null; + + /** @type {ConsecutiveRange} */ const range = new ConsecutiveRange(context.getSourceCode()); /** @@ -130,7 +139,7 @@ module.exports = { function reportIfUnreachable(node) { let nextNode = null; - if (node && currentCodePath.currentSegments.every(isUnreachable)) { + if (node && (node.type === "PropertyDefinition" || currentCodePath.currentSegments.every(isUnreachable))) { // Store this statement to distinguish consecutive statements. if (range.isEmpty) { @@ -212,6 +221,42 @@ module.exports = { "Program:exit"() { reportIfUnreachable(); + }, + + /* + * Instance fields defined in a subclass are never created if the constructor of the subclass + * doesn't call `super()`, so their definitions are unreachable code. + */ + "MethodDefinition[kind='constructor']"() { + constructorInfo = { + upper: constructorInfo, + hasSuperCall: false + }; + }, + "MethodDefinition[kind='constructor']:exit"(node) { + const { hasSuperCall } = constructorInfo; + + constructorInfo = constructorInfo.upper; + + // skip typescript constructors without the body + if (!node.value.body) { + return; + } + + const classDefinition = node.parent.parent; + + if (classDefinition.superClass && !hasSuperCall) { + for (const element of classDefinition.body.body) { + if (element.type === "PropertyDefinition" && !element.static) { + reportIfUnreachable(element); + } + } + } + }, + "CallExpression > Super.callee"() { + if (constructorInfo) { + constructorInfo.hasSuperCall = true; + } } }; } diff --git a/eslint/lib/rules/no-unsafe-finally.js b/eslint/lib/rules/no-unsafe-finally.js index 11bf06e..4bb7f7f 100644 --- a/eslint/lib/rules/no-unsafe-finally.js +++ b/eslint/lib/rules/no-unsafe-finally.js @@ -24,7 +24,6 @@ module.exports = { docs: { description: "disallow control flow statements in `finally` blocks", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/no-unsafe-finally" }, diff --git a/eslint/lib/rules/no-unsafe-negation.js b/eslint/lib/rules/no-unsafe-negation.js index a9c2ee7..c681986 100644 --- a/eslint/lib/rules/no-unsafe-negation.js +++ b/eslint/lib/rules/no-unsafe-negation.js @@ -52,12 +52,12 @@ module.exports = { docs: { description: "disallow negating the left operand of relational operators", - category: "Possible Errors", recommended: true, - url: "https://eslint.org/docs/rules/no-unsafe-negation", - suggestion: true + url: "https://eslint.org/docs/rules/no-unsafe-negation" }, + hasSuggestions: true, + schema: [ { type: "object", diff --git a/eslint/lib/rules/no-unsafe-optional-chaining.js b/eslint/lib/rules/no-unsafe-optional-chaining.js index 2eafc1a..cc15c99 100644 --- a/eslint/lib/rules/no-unsafe-optional-chaining.js +++ b/eslint/lib/rules/no-unsafe-optional-chaining.js @@ -24,8 +24,7 @@ module.exports = { docs: { description: "disallow use of optional chaining in contexts where the `undefined` value is not allowed", - category: "Possible Errors", - recommended: false, + recommended: true, url: "https://eslint.org/docs/rules/no-unsafe-optional-chaining" }, schema: [{ diff --git a/eslint/lib/rules/no-unused-expressions.js b/eslint/lib/rules/no-unused-expressions.js index 58c9b33..2081a51 100644 --- a/eslint/lib/rules/no-unused-expressions.js +++ b/eslint/lib/rules/no-unused-expressions.js @@ -30,7 +30,6 @@ module.exports = { docs: { description: "disallow unused expressions", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-unused-expressions" }, @@ -72,8 +71,8 @@ module.exports = { allowTaggedTemplates = config.allowTaggedTemplates || false, enforceForJSX = config.enforceForJSX || false; - // eslint-disable-next-line jsdoc/require-description /** + * Has AST suggesting a directive. * @param {ASTNode} node any node * @returns {boolean} whether the given node structurally represents a directive */ @@ -82,8 +81,8 @@ module.exports = { node.expression.type === "Literal" && typeof node.expression.value === "string"; } - // eslint-disable-next-line jsdoc/require-description /** + * Gets the leading sequence of members in a list that pass the predicate. * @param {Function} predicate ([a] -> Boolean) the function used to make the determination * @param {a[]} list the input list * @returns {a[]} the leading sequence of members in the given list that pass the given predicate @@ -97,8 +96,8 @@ module.exports = { return list.slice(); } - // eslint-disable-next-line jsdoc/require-description /** + * Gets leading directives nodes in a Node body. * @param {ASTNode} node a Program or BlockStatement node * @returns {ASTNode[]} the leading sequence of directive nodes in the given node's body */ @@ -106,8 +105,8 @@ module.exports = { return takeWhile(looksLikeDirective, node.body); } - // eslint-disable-next-line jsdoc/require-description /** + * Detect if a Node is a directive. * @param {ASTNode} node any node * @param {ASTNode[]} ancestors the given node's ancestors * @returns {boolean} whether the given node is considered a directive in its current position @@ -116,6 +115,12 @@ module.exports = { const parent = ancestors[ancestors.length - 1], grandparent = ancestors[ancestors.length - 2]; + /** + * https://tc39.es/ecma262/#directive-prologue + * + * Only `FunctionBody`, `ScriptBody` and `ModuleBody` can have directive prologue. + * Class static blocks do not have directive prologue. + */ return (parent.type === "Program" || parent.type === "BlockStatement" && (/Function/u.test(grandparent.type))) && directives(parent).indexOf(node) >= 0; diff --git a/eslint/lib/rules/no-unused-labels.js b/eslint/lib/rules/no-unused-labels.js index b33fcb7..f0b0961 100644 --- a/eslint/lib/rules/no-unused-labels.js +++ b/eslint/lib/rules/no-unused-labels.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "disallow unused labels", - category: "Best Practices", recommended: true, url: "https://eslint.org/docs/rules/no-unused-labels" }, diff --git a/eslint/lib/rules/no-unused-private-class-members.js b/eslint/lib/rules/no-unused-private-class-members.js new file mode 100644 index 0000000..74cf6ab --- /dev/null +++ b/eslint/lib/rules/no-unused-private-class-members.js @@ -0,0 +1,194 @@ +/** + * @fileoverview Rule to flag declared but unused private class members + * @author Tim van der Lippe + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: "problem", + + docs: { + description: "disallow unused private class members", + recommended: false, + url: "https://eslint.org/docs/rules/no-unused-private-class-members" + }, + + schema: [], + + messages: { + unusedPrivateClassMember: "'{{classMemberName}}' is defined but never used." + } + }, + + create(context) { + const trackedClasses = []; + + /** + * Check whether the current node is in a write only assignment. + * @param {ASTNode} privateIdentifierNode Node referring to a private identifier + * @returns {boolean} Whether the node is in a write only assignment + * @private + */ + function isWriteOnlyAssignment(privateIdentifierNode) { + const parentStatement = privateIdentifierNode.parent.parent; + const isAssignmentExpression = parentStatement.type === "AssignmentExpression"; + + if (!isAssignmentExpression && + parentStatement.type !== "ForInStatement" && + parentStatement.type !== "ForOfStatement" && + parentStatement.type !== "AssignmentPattern") { + return false; + } + + // It is a write-only usage, since we still allow usages on the right for reads + if (parentStatement.left !== privateIdentifierNode.parent) { + return false; + } + + // For any other operator (such as '+=') we still consider it a read operation + if (isAssignmentExpression && parentStatement.operator !== "=") { + + /* + * However, if the read operation is "discarded" in an empty statement, then + * we consider it write only. + */ + return parentStatement.parent.type === "ExpressionStatement"; + } + + return true; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + + // Collect all declared members up front and assume they are all unused + ClassBody(classBodyNode) { + const privateMembers = new Map(); + + trackedClasses.unshift(privateMembers); + for (const bodyMember of classBodyNode.body) { + if (bodyMember.type === "PropertyDefinition" || bodyMember.type === "MethodDefinition") { + if (bodyMember.key.type === "PrivateIdentifier") { + privateMembers.set(bodyMember.key.name, { + declaredNode: bodyMember, + isAccessor: bodyMember.type === "MethodDefinition" && + (bodyMember.kind === "set" || bodyMember.kind === "get") + }); + } + } + } + }, + + /* + * Process all usages of the private identifier and remove a member from + * `declaredAndUnusedPrivateMembers` if we deem it used. + */ + PrivateIdentifier(privateIdentifierNode) { + const classBody = trackedClasses.find(classProperties => classProperties.has(privateIdentifierNode.name)); + + // Can't happen, as it is a parser to have a missing class body, but let's code defensively here. + if (!classBody) { + return; + } + + // In case any other usage was already detected, we can short circuit the logic here. + const memberDefinition = classBody.get(privateIdentifierNode.name); + + if (memberDefinition.isUsed) { + return; + } + + // The definition of the class member itself + if (privateIdentifierNode.parent.type === "PropertyDefinition" || + privateIdentifierNode.parent.type === "MethodDefinition") { + return; + } + + /* + * Any usage of an accessor is considered a read, as the getter/setter can have + * side-effects in its definition. + */ + if (memberDefinition.isAccessor) { + memberDefinition.isUsed = true; + return; + } + + // Any assignments to this member, except for assignments that also read + if (isWriteOnlyAssignment(privateIdentifierNode)) { + return; + } + + const wrappingExpressionType = privateIdentifierNode.parent.parent.type; + const parentOfWrappingExpressionType = privateIdentifierNode.parent.parent.parent.type; + + // A statement which only increments (`this.#x++;`) + if (wrappingExpressionType === "UpdateExpression" && + parentOfWrappingExpressionType === "ExpressionStatement") { + return; + } + + /* + * ({ x: this.#usedInDestructuring } = bar); + * + * But should treat the following as a read: + * ({ [this.#x]: a } = foo); + */ + if (wrappingExpressionType === "Property" && + parentOfWrappingExpressionType === "ObjectPattern" && + privateIdentifierNode.parent.parent.value === privateIdentifierNode.parent) { + return; + } + + // [...this.#unusedInRestPattern] = bar; + if (wrappingExpressionType === "RestElement") { + return; + } + + // [this.#unusedInAssignmentPattern] = bar; + if (wrappingExpressionType === "ArrayPattern") { + return; + } + + /* + * We can't delete the memberDefinition, as we need to keep track of which member we are marking as used. + * In the case of nested classes, we only mark the first member we encounter as used. If you were to delete + * the member, then any subsequent usage could incorrectly mark the member of an encapsulating parent class + * as used, which is incorrect. + */ + memberDefinition.isUsed = true; + }, + + /* + * Post-process the class members and report any remaining members. + * Since private members can only be accessed in the current class context, + * we can safely assume that all usages are within the current class body. + */ + "ClassBody:exit"() { + const unusedPrivateMembers = trackedClasses.shift(); + + for (const [classMemberName, { declaredNode, isUsed }] of unusedPrivateMembers.entries()) { + if (isUsed) { + continue; + } + context.report({ + node: declaredNode, + loc: declaredNode.key.loc, + messageId: "unusedPrivateClassMember", + data: { + classMemberName: `#${classMemberName}` + } + }); + } + } + }; + } +}; diff --git a/eslint/lib/rules/no-unused-vars.js b/eslint/lib/rules/no-unused-vars.js index f04818f..847e21b 100644 --- a/eslint/lib/rules/no-unused-vars.js +++ b/eslint/lib/rules/no-unused-vars.js @@ -33,7 +33,6 @@ module.exports = { docs: { description: "disallow unused variables", - category: "Variables", recommended: true, url: "https://eslint.org/docs/rules/no-unused-vars" }, @@ -295,6 +294,31 @@ module.exports = { ); } + /** + * Checks whether a given node is unused expression or not. + * @param {ASTNode} node The node itself + * @returns {boolean} The node is an unused expression. + * @private + */ + function isUnusedExpression(node) { + const parent = node.parent; + + if (parent.type === "ExpressionStatement") { + return true; + } + + if (parent.type === "SequenceExpression") { + const isLastExpression = parent.expressions[parent.expressions.length - 1] === node; + + if (!isLastExpression) { + return true; + } + return isUnusedExpression(parent); + } + + return false; + } + /** * If a given reference is left-hand side of an assignment, this gets * the right-hand side node of the assignment. @@ -313,7 +337,6 @@ module.exports = { function getRhsNode(ref, prevRhsNode) { const id = ref.identifier; const parent = id.parent; - const grandparent = parent.parent; const refScope = ref.from.variableScope; const varScope = ref.resolved.scope.variableScope; const canBeUsedLater = refScope !== varScope || astUtils.isInLoop(id); @@ -327,7 +350,7 @@ module.exports = { } if (parent.type === "AssignmentExpression" && - grandparent.type === "ExpressionStatement" && + isUnusedExpression(parent) && id === parent.left && !canBeUsedLater ) { @@ -410,31 +433,6 @@ module.exports = { ); } - /** - * Checks whether a given node is unused expression or not. - * @param {ASTNode} node The node itself - * @returns {boolean} The node is an unused expression. - * @private - */ - function isUnusedExpression(node) { - const parent = node.parent; - - if (parent.type === "ExpressionStatement") { - return true; - } - - if (parent.type === "SequenceExpression") { - const isLastExpression = parent.expressions[parent.expressions.length - 1] === node; - - if (!isLastExpression) { - return true; - } - return isUnusedExpression(parent); - } - - return false; - } - /** * Checks whether a given reference is a read to update itself or not. * @param {eslint-scope.Reference} ref A reference to check. diff --git a/eslint/lib/rules/no-use-before-define.js b/eslint/lib/rules/no-use-before-define.js index c730056..7f904f4 100644 --- a/eslint/lib/rules/no-use-before-define.js +++ b/eslint/lib/rules/no-use-before-define.js @@ -34,52 +34,113 @@ function parseOptions(options) { } /** - * Checks whether or not a given variable is a function declaration. - * @param {eslint-scope.Variable} variable A variable to check. - * @returns {boolean} `true` if the variable is a function declaration. + * Checks whether or not a given location is inside of the range of a given node. + * @param {ASTNode} node An node to check. + * @param {number} location A location to check. + * @returns {boolean} `true` if the location is inside of the range of the node. */ -function isFunction(variable) { - return variable.defs[0].type === "FunctionName"; +function isInRange(node, location) { + return node && node.range[0] <= location && location <= node.range[1]; } /** - * Checks whether or not a given variable is a class declaration in an upper function scope. - * @param {eslint-scope.Variable} variable A variable to check. - * @param {eslint-scope.Reference} reference A reference to check. - * @returns {boolean} `true` if the variable is a class declaration. + * Checks whether or not a given location is inside of the range of a class static initializer. + * Static initializers are static blocks and initializers of static fields. + * @param {ASTNode} node `ClassBody` node to check static initializers. + * @param {number} location A location to check. + * @returns {boolean} `true` if the location is inside of a class static initializer. */ -function isOuterClass(variable, reference) { - return ( - variable.defs[0].type === "ClassName" && - variable.scope.variableScope !== reference.from.variableScope - ); +function isInClassStaticInitializerRange(node, location) { + return node.body.some(classMember => ( + ( + classMember.type === "StaticBlock" && + isInRange(classMember, location) + ) || + ( + classMember.type === "PropertyDefinition" && + classMember.static && + classMember.value && + isInRange(classMember.value, location) + ) + )); } /** - * Checks whether or not a given variable is a variable declaration in an upper function scope. - * @param {eslint-scope.Variable} variable A variable to check. - * @param {eslint-scope.Reference} reference A reference to check. - * @returns {boolean} `true` if the variable is a variable declaration. + * Checks whether a given scope is the scope of a a class static initializer. + * Static initializers are static blocks and initializers of static fields. + * @param {eslint-scope.Scope} scope A scope to check. + * @returns {boolean} `true` if the scope is a class static initializer scope. */ -function isOuterVariable(variable, reference) { - return ( - variable.defs[0].type === "Variable" && - variable.scope.variableScope !== reference.from.variableScope - ); +function isClassStaticInitializerScope(scope) { + if (scope.type === "class-static-block") { + return true; + } + + if (scope.type === "class-field-initializer") { + + // `scope.block` is PropertyDefinition#value node + const propertyDefinition = scope.block.parent; + + return propertyDefinition.static; + } + + return false; } /** - * Checks whether or not a given location is inside of the range of a given node. - * @param {ASTNode} node An node to check. - * @param {number} location A location to check. - * @returns {boolean} `true` if the location is inside of the range of the node. + * Checks whether a given reference is evaluated in an execution context + * that isn't the one where the variable it refers to is defined. + * Execution contexts are: + * - top-level + * - functions + * - class field initializers (implicit functions) + * - class static blocks (implicit functions) + * Static class field initializers and class static blocks are automatically run during the class definition evaluation, + * and therefore we'll consider them as a part of the parent execution context. + * Example: + * + * const x = 1; + * + * x; // returns `false` + * () => x; // returns `true` + * + * class C { + * field = x; // returns `true` + * static field = x; // returns `false` + * + * method() { + * x; // returns `true` + * } + * + * static method() { + * x; // returns `true` + * } + * + * static { + * x; // returns `false` + * } + * } + * @param {eslint-scope.Reference} reference A reference to check. + * @returns {boolean} `true` if the reference is from a separate execution context. */ -function isInRange(node, location) { - return node && node.range[0] <= location && location <= node.range[1]; +function isFromSeparateExecutionContext(reference) { + const variable = reference.resolved; + let scope = reference.from; + + // Scope#variableScope represents execution context + while (variable.scope.variableScope !== scope.variableScope) { + if (isClassStaticInitializerScope(scope.variableScope)) { + scope = scope.variableScope.upper; + } else { + return true; + } + } + + return false; } /** - * Checks whether or not a given reference is inside of the initializers of a given variable. + * Checks whether or not a given reference is evaluated during the initialization of its variable. * * This returns `true` in the following cases: * @@ -88,17 +149,45 @@ function isInRange(node, location) { * var {a = a} = obj * for (var a in a) {} * for (var a of a) {} - * @param {Variable} variable A variable to check. + * var C = class { [C]; }; + * var C = class { static foo = C; }; + * var C = class { static { foo = C; } }; + * class C extends C {} + * class C extends (class { static foo = C; }) {} + * class C { [C]; } * @param {Reference} reference A reference to check. - * @returns {boolean} `true` if the reference is inside of the initializers. + * @returns {boolean} `true` if the reference is evaluated during the initialization. */ -function isInInitializer(variable, reference) { - if (variable.scope !== reference.from) { +function isEvaluatedDuringInitialization(reference) { + if (isFromSeparateExecutionContext(reference)) { + + /* + * Even if the reference appears in the initializer, it isn't evaluated during the initialization. + * For example, `const x = () => x;` is valid. + */ return false; } - let node = variable.identifiers[0].parent; const location = reference.identifier.range[1]; + const definition = reference.resolved.defs[0]; + + if (definition.type === "ClassName") { + + // `ClassDeclaration` or `ClassExpression` + const classDefinition = definition.node; + + return ( + isInRange(classDefinition, location) && + + /* + * Class binding is initialized before running static initializers. + * For example, `class C { static foo = C; static { bar = C; } }` is valid. + */ + !isInClassStaticInitializerRange(classDefinition.body, location) + ); + } + + let node = definition.name.parent; while (node) { if (node.type === "VariableDeclarator") { @@ -135,7 +224,6 @@ module.exports = { docs: { description: "disallow the use of variables before they are defined", - category: "Variables", recommended: false, url: "https://eslint.org/docs/rules/no-use-before-define" }, @@ -168,65 +256,77 @@ module.exports = { const options = parseOptions(context.options[0]); /** - * Determines whether a given use-before-define case should be reported according to the options. - * @param {eslint-scope.Variable} variable The variable that gets used before being defined - * @param {eslint-scope.Reference} reference The reference to the variable - * @returns {boolean} `true` if the usage should be reported + * Determines whether a given reference should be checked. + * + * Returns `false` if the reference is: + * - initialization's (e.g., `let a = 1`). + * - referring to an undefined variable (i.e., if it's an unresolved reference). + * - referring to a variable that is defined, but not in the given source code + * (e.g., global environment variable or `arguments` in functions). + * - allowed by options. + * @param {eslint-scope.Reference} reference The reference + * @returns {boolean} `true` if the reference should be checked */ - function isForbidden(variable, reference) { - if (isFunction(variable)) { - return options.functions; + function shouldCheck(reference) { + if (reference.init) { + return false; } - if (isOuterClass(variable, reference)) { - return options.classes; + + const variable = reference.resolved; + + if (!variable || variable.defs.length === 0) { + return false; } - if (isOuterVariable(variable, reference)) { - return options.variables; + + const definitionType = variable.defs[0].type; + + if (!options.functions && definitionType === "FunctionName") { + return false; } + + if ( + ( + !options.variables && definitionType === "Variable" || + !options.classes && definitionType === "ClassName" + ) && + + // don't skip checking the reference if it's in the same execution context, because of TDZ + isFromSeparateExecutionContext(reference) + ) { + return false; + } + return true; } /** - * Finds and validates all variables in a given scope. - * @param {Scope} scope The scope object. + * Finds and validates all references in a given scope and its child scopes. + * @param {eslint-scope.Scope} scope The scope object. * @returns {void} - * @private */ - function findVariablesInScope(scope) { - scope.references.forEach(reference => { + function checkReferencesInScope(scope) { + scope.references.filter(shouldCheck).forEach(reference => { const variable = reference.resolved; + const definitionIdentifier = variable.defs[0].name; - /* - * Skips when the reference is: - * - initialization's. - * - referring to an undefined variable. - * - referring to a global environment variable (there're no identifiers). - * - located preceded by the variable (except in initializers). - * - allowed by options. - */ - if (reference.init || - !variable || - variable.identifiers.length === 0 || - (variable.identifiers[0].range[1] < reference.identifier.range[1] && !isInInitializer(variable, reference)) || - !isForbidden(variable, reference) + if ( + reference.identifier.range[1] < definitionIdentifier.range[1] || + isEvaluatedDuringInitialization(reference) ) { - return; + context.report({ + node: reference.identifier, + messageId: "usedBeforeDefined", + data: reference.identifier + }); } - - // Reports. - context.report({ - node: reference.identifier, - messageId: "usedBeforeDefined", - data: reference.identifier - }); }); - scope.childScopes.forEach(findVariablesInScope); + scope.childScopes.forEach(checkReferencesInScope); } return { Program() { - findVariablesInScope(context.getScope()); + checkReferencesInScope(context.getScope()); } }; } diff --git a/eslint/lib/rules/no-useless-backreference.js b/eslint/lib/rules/no-useless-backreference.js index 529c164..ae49147 100644 --- a/eslint/lib/rules/no-useless-backreference.js +++ b/eslint/lib/rules/no-useless-backreference.js @@ -64,8 +64,7 @@ module.exports = { docs: { description: "disallow useless backreferences in regular expressions", - category: "Possible Errors", - recommended: false, + recommended: true, url: "https://eslint.org/docs/rules/no-useless-backreference" }, diff --git a/eslint/lib/rules/no-useless-call.js b/eslint/lib/rules/no-useless-call.js index b1382a2..8935066 100644 --- a/eslint/lib/rules/no-useless-call.js +++ b/eslint/lib/rules/no-useless-call.js @@ -55,7 +55,6 @@ module.exports = { docs: { description: "disallow unnecessary calls to `.call()` and `.apply()`", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-useless-call" }, diff --git a/eslint/lib/rules/no-useless-catch.js b/eslint/lib/rules/no-useless-catch.js index f303c27..280ba55 100644 --- a/eslint/lib/rules/no-useless-catch.js +++ b/eslint/lib/rules/no-useless-catch.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "disallow unnecessary `catch` clauses", - category: "Best Practices", recommended: true, url: "https://eslint.org/docs/rules/no-useless-catch" }, diff --git a/eslint/lib/rules/no-useless-computed-key.js b/eslint/lib/rules/no-useless-computed-key.js index a1cacc2..a876921 100644 --- a/eslint/lib/rules/no-useless-computed-key.js +++ b/eslint/lib/rules/no-useless-computed-key.js @@ -10,6 +10,77 @@ const astUtils = require("./utils/ast-utils"); +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Determines whether the computed key syntax is unnecessarily used for the given node. + * In particular, it determines whether removing the square brackets and using the content between them + * directly as the key (e.g. ['foo'] -> 'foo') would produce valid syntax and preserve the same behavior. + * Valid non-computed keys are only: identifiers, number literals and string literals. + * Only literals can preserve the same behavior, with a few exceptions for specific node types: + * Property + * - { ["__proto__"]: foo } defines a property named "__proto__" + * { "__proto__": foo } defines object's prototype + * PropertyDefinition + * - class C { ["constructor"]; } defines an instance field named "constructor" + * class C { "constructor"; } produces a parsing error + * - class C { static ["constructor"]; } defines a static field named "constructor" + * class C { static "constructor"; } produces a parsing error + * - class C { static ["prototype"]; } produces a runtime error (doesn't break the whole script) + * class C { static "prototype"; } produces a parsing error (breaks the whole script) + * MethodDefinition + * - class C { ["constructor"]() {} } defines a prototype method named "constructor" + * class C { "constructor"() {} } defines the constructor + * - class C { static ["prototype"]() {} } produces a runtime error (doesn't break the whole script) + * class C { static "prototype"() {} } produces a parsing error (breaks the whole script) + * @param {ASTNode} node The node to check. It can be `Property`, `PropertyDefinition` or `MethodDefinition`. + * @throws {Error} (Unreachable.) + * @returns {void} `true` if the node has useless computed key. + */ +function hasUselessComputedKey(node) { + if (!node.computed) { + return false; + } + + const { key } = node; + + if (key.type !== "Literal") { + return false; + } + + const { value } = key; + + if (typeof value !== "number" && typeof value !== "string") { + return false; + } + + switch (node.type) { + case "Property": + return value !== "__proto__"; + + case "PropertyDefinition": + if (node.static) { + return value !== "constructor" && value !== "prototype"; + } + + return value !== "constructor"; + + case "MethodDefinition": + if (node.static) { + return value !== "prototype"; + } + + return value !== "constructor"; + + /* istanbul ignore next */ + default: + throw new Error(`Unexpected node type: ${node.type}`); + } + +} + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -20,7 +91,6 @@ module.exports = { docs: { description: "disallow unnecessary computed property keys in objects and classes", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/no-useless-computed-key" }, @@ -51,22 +121,9 @@ module.exports = { * @returns {void} */ function check(node) { - if (!node.computed) { - return; - } - - const key = node.key, - nodeType = typeof key.value; - - let allowedKey; - - if (node.type === "MethodDefinition") { - allowedKey = node.static ? "prototype" : "constructor"; - } else { - allowedKey = "__proto__"; - } + if (hasUselessComputedKey(node)) { + const { key } = node; - if (key.type === "Literal" && (nodeType === "string" || nodeType === "number") && key.value !== allowedKey) { context.report({ node, messageId: "unnecessarilyComputedProperty", @@ -103,7 +160,8 @@ module.exports = { return { Property: check, - MethodDefinition: enforceForClassMembers ? check : noop + MethodDefinition: enforceForClassMembers ? check : noop, + PropertyDefinition: enforceForClassMembers ? check : noop }; } }; diff --git a/eslint/lib/rules/no-useless-concat.js b/eslint/lib/rules/no-useless-concat.js index cfc60c8..a0176a7 100644 --- a/eslint/lib/rules/no-useless-concat.js +++ b/eslint/lib/rules/no-useless-concat.js @@ -70,7 +70,6 @@ module.exports = { docs: { description: "disallow unnecessary concatenation of literals or template literals", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-useless-concat" }, diff --git a/eslint/lib/rules/no-useless-constructor.js b/eslint/lib/rules/no-useless-constructor.js index baabe7e..13ec675 100644 --- a/eslint/lib/rules/no-useless-constructor.js +++ b/eslint/lib/rules/no-useless-constructor.js @@ -138,7 +138,6 @@ module.exports = { docs: { description: "disallow unnecessary constructors", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/no-useless-constructor" }, diff --git a/eslint/lib/rules/no-useless-escape.js b/eslint/lib/rules/no-useless-escape.js index 512c93a..a780a7a 100644 --- a/eslint/lib/rules/no-useless-escape.js +++ b/eslint/lib/rules/no-useless-escape.js @@ -34,16 +34,17 @@ const REGEX_NON_CHARCLASS_ESCAPES = union(REGEX_GENERAL_ESCAPES, new Set("^/.$*+ * @returns {Object[]} A list of characters, each with info on escaping and whether they're in a character class. * @example * - * parseRegExp('a\\b[cd-]') + * parseRegExp("a\\b[cd-]"); * - * returns: + * // returns: * [ - * {text: 'a', index: 0, escaped: false, inCharClass: false, startsCharClass: false, endsCharClass: false}, - * {text: 'b', index: 2, escaped: true, inCharClass: false, startsCharClass: false, endsCharClass: false}, - * {text: 'c', index: 4, escaped: false, inCharClass: true, startsCharClass: true, endsCharClass: false}, - * {text: 'd', index: 5, escaped: false, inCharClass: true, startsCharClass: false, endsCharClass: false}, - * {text: '-', index: 6, escaped: false, inCharClass: true, startsCharClass: false, endsCharClass: false} - * ] + * { text: "a", index: 0, escaped: false, inCharClass: false, startsCharClass: false, endsCharClass: false }, + * { text: "b", index: 2, escaped: true, inCharClass: false, startsCharClass: false, endsCharClass: false }, + * { text: "c", index: 4, escaped: false, inCharClass: true, startsCharClass: true, endsCharClass: false }, + * { text: "d", index: 5, escaped: false, inCharClass: true, startsCharClass: false, endsCharClass: false }, + * { text: "-", index: 6, escaped: false, inCharClass: true, startsCharClass: false, endsCharClass: false } + * ]; + * */ function parseRegExp(regExpText) { const charList = []; @@ -83,12 +84,12 @@ module.exports = { docs: { description: "disallow unnecessary escape characters", - category: "Best Practices", recommended: true, - url: "https://eslint.org/docs/rules/no-useless-escape", - suggestion: true + url: "https://eslint.org/docs/rules/no-useless-escape" }, + hasSuggestions: true, + messages: { unnecessaryEscape: "Unnecessary escape character: \\{{character}}.", removeEscape: "Remove the `\\`. This maintains the current functionality.", diff --git a/eslint/lib/rules/no-useless-rename.js b/eslint/lib/rules/no-useless-rename.js index a7cec02..c0d27e6 100644 --- a/eslint/lib/rules/no-useless-rename.js +++ b/eslint/lib/rules/no-useless-rename.js @@ -21,7 +21,6 @@ module.exports = { docs: { description: "disallow renaming import, export, and destructured assignments to the same name", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/no-useless-rename" }, diff --git a/eslint/lib/rules/no-useless-return.js b/eslint/lib/rules/no-useless-return.js index 111cb21..87f0589 100644 --- a/eslint/lib/rules/no-useless-return.js +++ b/eslint/lib/rules/no-useless-return.js @@ -67,7 +67,6 @@ module.exports = { docs: { description: "disallow redundant return statements", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-useless-return" }, diff --git a/eslint/lib/rules/no-var.js b/eslint/lib/rules/no-var.js index f2cb96b..a821c38 100644 --- a/eslint/lib/rules/no-var.js +++ b/eslint/lib/rules/no-var.js @@ -185,7 +185,6 @@ module.exports = { docs: { description: "require `let` or `const` instead of `var`", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/no-var" }, diff --git a/eslint/lib/rules/no-void.js b/eslint/lib/rules/no-void.js index 99c8378..dba4932 100644 --- a/eslint/lib/rules/no-void.js +++ b/eslint/lib/rules/no-void.js @@ -14,7 +14,6 @@ module.exports = { docs: { description: "disallow `void` operators", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-void" }, diff --git a/eslint/lib/rules/no-warning-comments.js b/eslint/lib/rules/no-warning-comments.js index e5f702b..23e3da3 100644 --- a/eslint/lib/rules/no-warning-comments.js +++ b/eslint/lib/rules/no-warning-comments.js @@ -20,7 +20,6 @@ module.exports = { docs: { description: "disallow specified warning terms in comments", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/no-warning-comments" }, diff --git a/eslint/lib/rules/no-whitespace-before-property.js b/eslint/lib/rules/no-whitespace-before-property.js index 226f873..9a49299 100644 --- a/eslint/lib/rules/no-whitespace-before-property.js +++ b/eslint/lib/rules/no-whitespace-before-property.js @@ -20,7 +20,6 @@ module.exports = { docs: { description: "disallow whitespace before properties", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/no-whitespace-before-property" }, diff --git a/eslint/lib/rules/no-with.js b/eslint/lib/rules/no-with.js index d3e52e0..219a680 100644 --- a/eslint/lib/rules/no-with.js +++ b/eslint/lib/rules/no-with.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "disallow `with` statements", - category: "Best Practices", recommended: true, url: "https://eslint.org/docs/rules/no-with" }, diff --git a/eslint/lib/rules/nonblock-statement-body-position.js b/eslint/lib/rules/nonblock-statement-body-position.js index 34e6eea..7ed541b 100644 --- a/eslint/lib/rules/nonblock-statement-body-position.js +++ b/eslint/lib/rules/nonblock-statement-body-position.js @@ -16,7 +16,6 @@ module.exports = { docs: { description: "enforce the location of single-line statements", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/nonblock-statement-body-position" }, diff --git a/eslint/lib/rules/object-curly-newline.js b/eslint/lib/rules/object-curly-newline.js index 1fbea00..e160991 100644 --- a/eslint/lib/rules/object-curly-newline.js +++ b/eslint/lib/rules/object-curly-newline.js @@ -150,7 +150,6 @@ module.exports = { docs: { description: "enforce consistent line breaks after opening and before closing braces", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/object-curly-newline" }, diff --git a/eslint/lib/rules/object-curly-spacing.js b/eslint/lib/rules/object-curly-spacing.js index c0044f5..b18ef57 100644 --- a/eslint/lib/rules/object-curly-spacing.js +++ b/eslint/lib/rules/object-curly-spacing.js @@ -16,7 +16,6 @@ module.exports = { docs: { description: "enforce consistent spacing inside braces", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/object-curly-spacing" }, diff --git a/eslint/lib/rules/object-property-newline.js b/eslint/lib/rules/object-property-newline.js index 0c7f800..7cca23f 100644 --- a/eslint/lib/rules/object-property-newline.js +++ b/eslint/lib/rules/object-property-newline.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "enforce placing object properties on separate lines", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/object-property-newline" }, diff --git a/eslint/lib/rules/object-shorthand.js b/eslint/lib/rules/object-shorthand.js index 3999ff8..10bb07b 100644 --- a/eslint/lib/rules/object-shorthand.js +++ b/eslint/lib/rules/object-shorthand.js @@ -28,7 +28,6 @@ module.exports = { docs: { description: "require or disallow method and property shorthand syntax for object literals", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/object-shorthand" }, @@ -149,7 +148,6 @@ module.exports = { * @param {ASTNode} property Property AST node * @returns {boolean} True if the property can have a shorthand form * @private - * */ function canHaveShorthand(property) { return (property.kind !== "set" && property.kind !== "get" && property.type !== "SpreadElement" && property.type !== "SpreadProperty" && property.type !== "ExperimentalSpreadProperty"); @@ -157,7 +155,7 @@ module.exports = { /** * Checks whether a node is a string literal. - * @param {ASTNode} node Any AST node. + * @param {ASTNode} node Any AST node. * @returns {boolean} `true` if it is a string literal. */ function isStringLiteral(node) { @@ -169,7 +167,6 @@ module.exports = { * @param {ASTNode} property Property AST node * @returns {boolean} True if the property is considered shorthand, false if not. * @private - * */ function isShorthand(property) { @@ -182,7 +179,6 @@ module.exports = { * @param {ASTNode} property Property AST node * @returns {boolean} True if the key and value are named equally, false if not. * @private - * */ function isRedundant(property) { const value = property.value; @@ -199,10 +195,9 @@ module.exports = { /** * Ensures that an object's properties are consistently shorthand, or not shorthand at all. - * @param {ASTNode} node Property AST node - * @param {boolean} checkRedundancy Whether to check longform redundancy + * @param {ASTNode} node Property AST node + * @param {boolean} checkRedundancy Whether to check longform redundancy * @returns {void} - * */ function checkConsistency(node, checkRedundancy) { diff --git a/eslint/lib/rules/one-var-declaration-per-line.js b/eslint/lib/rules/one-var-declaration-per-line.js index db46747..c0ad700 100644 --- a/eslint/lib/rules/one-var-declaration-per-line.js +++ b/eslint/lib/rules/one-var-declaration-per-line.js @@ -14,7 +14,6 @@ module.exports = { docs: { description: "require or disallow newlines around variable declarations", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/one-var-declaration-per-line" }, diff --git a/eslint/lib/rules/one-var.js b/eslint/lib/rules/one-var.js index e3df832..daff2d2 100644 --- a/eslint/lib/rules/one-var.js +++ b/eslint/lib/rules/one-var.js @@ -34,7 +34,6 @@ module.exports = { docs: { description: "enforce variables to be declared either together or separately in functions", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/one-var" }, @@ -209,7 +208,7 @@ module.exports = { /** * Determines the current scope (function or block) - * @param {string} statementType node.kind, one of: "var", "let", or "const" + * @param {string} statementType node.kind, one of: "var", "let", or "const" * @returns {Object} The scope associated with statementType */ function getCurrentScope(statementType) { @@ -542,6 +541,8 @@ module.exports = { FunctionDeclaration: startFunction, FunctionExpression: startFunction, ArrowFunctionExpression: startFunction, + StaticBlock: startFunction, // StaticBlock creates a new scope for `var` variables + BlockStatement: startBlock, ForStatement: startBlock, ForInStatement: startBlock, @@ -553,10 +554,12 @@ module.exports = { "ForInStatement:exit": endBlock, "SwitchStatement:exit": endBlock, "BlockStatement:exit": endBlock, + "Program:exit": endFunction, "FunctionDeclaration:exit": endFunction, "FunctionExpression:exit": endFunction, - "ArrowFunctionExpression:exit": endFunction + "ArrowFunctionExpression:exit": endFunction, + "StaticBlock:exit": endFunction }; } diff --git a/eslint/lib/rules/operator-assignment.js b/eslint/lib/rules/operator-assignment.js index fdb0884..34bdb86 100644 --- a/eslint/lib/rules/operator-assignment.js +++ b/eslint/lib/rules/operator-assignment.js @@ -17,8 +17,8 @@ const astUtils = require("./utils/ast-utils"); /** * Checks whether an operator is commutative and has an operator assignment * shorthand form. - * @param {string} operator Operator to check. - * @returns {boolean} True if the operator is commutative and has a + * @param {string} operator Operator to check. + * @returns {boolean} True if the operator is commutative and has a * shorthand form. */ function isCommutativeOperatorWithShorthand(operator) { @@ -28,8 +28,8 @@ function isCommutativeOperatorWithShorthand(operator) { /** * Checks whether an operator is not commutative and has an operator assignment * shorthand form. - * @param {string} operator Operator to check. - * @returns {boolean} True if the operator is not commutative and has + * @param {string} operator Operator to check. + * @returns {boolean} True if the operator is not commutative and has * a shorthand form. */ function isNonCommutativeOperatorWithShorthand(operator) { @@ -63,7 +63,6 @@ module.exports = { docs: { description: "require or disallow assignment operator shorthand where possible", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/operator-assignment" }, @@ -76,8 +75,8 @@ module.exports = { fixable: "code", messages: { - replaced: "Assignment can be replaced with operator assignment.", - unexpected: "Unexpected operator assignment shorthand." + replaced: "Assignment (=) can be replaced with operator assignment ({{operator}}=).", + unexpected: "Unexpected operator assignment ({{operator}}=) shorthand." } }, @@ -96,7 +95,7 @@ module.exports = { /** * Ensures that an assignment uses the shorthand form where possible. - * @param {ASTNode} node An AssignmentExpression node. + * @param {ASTNode} node An AssignmentExpression node. * @returns {void} */ function verify(node) { @@ -113,6 +112,7 @@ module.exports = { context.report({ node, messageId: "replaced", + data: { operator }, fix(fixer) { if (canBeFixed(left) && canBeFixed(expr.left)) { const equalsToken = getOperatorToken(node); @@ -139,7 +139,8 @@ module.exports = { */ context.report({ node, - messageId: "replaced" + messageId: "replaced", + data: { operator } }); } } @@ -147,7 +148,7 @@ module.exports = { /** * Warns if an assignment expression uses operator assignment shorthand. - * @param {ASTNode} node An AssignmentExpression node. + * @param {ASTNode} node An AssignmentExpression node. * @returns {void} */ function prohibit(node) { @@ -155,6 +156,7 @@ module.exports = { context.report({ node, messageId: "unexpected", + data: { operator: node.operator }, fix(fixer) { if (canBeFixed(node.left)) { const firstToken = sourceCode.getFirstToken(node); diff --git a/eslint/lib/rules/operator-linebreak.js b/eslint/lib/rules/operator-linebreak.js index 18da5c5..6eab0cc 100644 --- a/eslint/lib/rules/operator-linebreak.js +++ b/eslint/lib/rules/operator-linebreak.js @@ -21,7 +21,6 @@ module.exports = { docs: { description: "enforce consistent linebreak style for operators", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/operator-linebreak" }, @@ -136,23 +135,21 @@ module.exports = { /** * Checks the operator placement * @param {ASTNode} node The node to check - * @param {ASTNode} leftSide The node that comes before the operator in `node` + * @param {ASTNode} rightSide The node that comes after the operator in `node` + * @param {string} operator The operator * @private * @returns {void} */ - function validateNode(node, leftSide) { + function validateNode(node, rightSide, operator) { /* - * When the left part of a binary expression is a single expression wrapped in - * parentheses (ex: `(a) + b`), leftToken will be the last token of the expression - * and operatorToken will be the closing parenthesis. - * The leftToken should be the last closing parenthesis, and the operatorToken - * should be the token right after that. + * Find the operator token by searching from the right side, because between the left side and the operator + * there could be additional tokens from type annotations. Search specifically for the token which + * value equals the operator, in order to skip possible opening parentheses before the right side node. */ - const operatorToken = sourceCode.getTokenAfter(leftSide, astUtils.isNotClosingParenToken); + const operatorToken = sourceCode.getTokenBefore(rightSide, token => token.value === operator); const leftToken = sourceCode.getTokenBefore(operatorToken); const rightToken = sourceCode.getTokenAfter(operatorToken); - const operator = operatorToken.value; const operatorStyleOverride = styleOverrides[operator]; const style = operatorStyleOverride || globalStyle; const fix = getFixer(operatorToken, style); @@ -222,7 +219,7 @@ module.exports = { * @returns {void} */ function validateBinaryExpression(node) { - validateNode(node, node.left); + validateNode(node, node.right, node.operator); } //-------------------------------------------------------------------------- @@ -235,12 +232,17 @@ module.exports = { AssignmentExpression: validateBinaryExpression, VariableDeclarator(node) { if (node.init) { - validateNode(node, node.id); + validateNode(node, node.init, "="); + } + }, + PropertyDefinition(node) { + if (node.value) { + validateNode(node, node.value, "="); } }, ConditionalExpression(node) { - validateNode(node, node.test); - validateNode(node, node.consequent); + validateNode(node, node.consequent, "?"); + validateNode(node, node.alternate, ":"); } }; } diff --git a/eslint/lib/rules/padded-blocks.js b/eslint/lib/rules/padded-blocks.js index f8b5bd9..de75e8d 100644 --- a/eslint/lib/rules/padded-blocks.js +++ b/eslint/lib/rules/padded-blocks.js @@ -21,7 +21,6 @@ module.exports = { docs: { description: "require or disallow padding within blocks", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/padded-blocks" }, @@ -107,6 +106,12 @@ module.exports = { if (node.type === "SwitchStatement") { return sourceCode.getTokenBefore(node.cases[0]); } + + if (node.type === "StaticBlock") { + return sourceCode.getFirstToken(node, { skip: 1 }); // skip the `static` token + } + + // `BlockStatement` or `ClassBody` return sourceCode.getFirstToken(node); } @@ -167,11 +172,13 @@ module.exports = { /** * Checks if a node should be padded, according to the rule config. * @param {ASTNode} node The AST node to check. + * @throws {Error} (Unreachable) * @returns {boolean} True if the node should be padded, false otherwise. */ function requirePaddingFor(node) { switch (node.type) { case "BlockStatement": + case "StaticBlock": return options.blocks; case "SwitchStatement": return options.switches; @@ -282,6 +289,7 @@ module.exports = { } checkPadding(node); }; + rule.StaticBlock = rule.BlockStatement; } if (Object.prototype.hasOwnProperty.call(options, "classes")) { diff --git a/eslint/lib/rules/padding-line-between-statements.js b/eslint/lib/rules/padding-line-between-statements.js index c97b995..42859dd 100644 --- a/eslint/lib/rules/padding-line-between-statements.js +++ b/eslint/lib/rules/padding-line-between-statements.js @@ -431,7 +431,6 @@ module.exports = { docs: { description: "require or disallow padding lines between statements", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/padding-line-between-statements" }, @@ -619,9 +618,11 @@ module.exports = { Program: enterScope, BlockStatement: enterScope, SwitchStatement: enterScope, + StaticBlock: enterScope, "Program:exit": exitScope, "BlockStatement:exit": exitScope, "SwitchStatement:exit": exitScope, + "StaticBlock:exit": exitScope, ":statement": verify, diff --git a/eslint/lib/rules/prefer-arrow-callback.js b/eslint/lib/rules/prefer-arrow-callback.js index ee5cfe3..518bf4b 100644 --- a/eslint/lib/rules/prefer-arrow-callback.js +++ b/eslint/lib/rules/prefer-arrow-callback.js @@ -60,6 +60,7 @@ function getVariableOfArguments(scope) { /** * Checks whether or not a given node is a callback. * @param {ASTNode} node A node to check. + * @throws {Error} (Unreachable.) * @returns {Object} * {boolean} retv.isCallback - `true` if the node is a callback. * {boolean} retv.isLexicalThis - `true` if the node is with `.bind(this)`. @@ -150,7 +151,6 @@ module.exports = { docs: { description: "require using arrow functions for callbacks", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/prefer-arrow-callback" }, @@ -295,7 +295,7 @@ 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; // eslint-disable-line eslint-plugin/fixer-return -- false positive + return; } // Remove `.bind(this)` if exists. @@ -307,7 +307,7 @@ module.exports = { * E.g. `(foo || function(){}).bind(this)` */ if (memberNode.type !== "MemberExpression") { - return; // eslint-disable-line eslint-plugin/fixer-return -- false positive + return; } const callNode = memberNode.parent; @@ -320,12 +320,12 @@ module.exports = { * ^^^^^^^^^^^^ */ if (astUtils.isParenthesised(sourceCode, memberNode)) { - return; // eslint-disable-line eslint-plugin/fixer-return -- false positive + return; } // If comments exist in the `.bind(this)`, don't remove those. if (sourceCode.commentsExistBetween(firstTokenToRemove, lastTokenToRemove)) { - return; // eslint-disable-line eslint-plugin/fixer-return -- false positive + return; } yield fixer.removeRange([firstTokenToRemove.range[0], lastTokenToRemove.range[1]]); diff --git a/eslint/lib/rules/prefer-const.js b/eslint/lib/rules/prefer-const.js index f6e79e7..38ec973 100644 --- a/eslint/lib/rules/prefer-const.js +++ b/eslint/lib/rules/prefer-const.js @@ -17,7 +17,7 @@ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ const PATTERN_TYPE = /^(?:.+?Pattern|RestElement|SpreadProperty|ExperimentalRestProperty|Property)$/u; -const DECLARATION_HOST_TYPE = /^(?:Program|BlockStatement|SwitchCase)$/u; +const DECLARATION_HOST_TYPE = /^(?:Program|BlockStatement|StaticBlock|SwitchCase)$/u; const DESTRUCTURING_HOST_TYPE = /^(?:VariableDeclarator|AssignmentExpression)$/u; /** @@ -332,7 +332,6 @@ module.exports = { docs: { description: "require `const` declarations for variables that are never reassigned after declared", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/prefer-const" }, diff --git a/eslint/lib/rules/prefer-destructuring.js b/eslint/lib/rules/prefer-destructuring.js index f82bb75..46986d2 100644 --- a/eslint/lib/rules/prefer-destructuring.js +++ b/eslint/lib/rules/prefer-destructuring.js @@ -26,7 +26,6 @@ module.exports = { docs: { description: "require destructuring from arrays and/or objects", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/prefer-destructuring" }, @@ -119,8 +118,8 @@ module.exports = { // Helpers //-------------------------------------------------------------------------- - // eslint-disable-next-line jsdoc/require-description /** + * Checks if destructuring type should be checked. * @param {string} nodeType "AssignmentExpression" or "VariableDeclarator" * @param {string} destructuringType "array" or "object" * @returns {boolean} `true` if the destructuring type should be checked for the given node @@ -221,7 +220,11 @@ module.exports = { * @returns {void} */ function performCheck(leftNode, rightNode, reportNode) { - if (rightNode.type !== "MemberExpression" || rightNode.object.type === "Super") { + if ( + rightNode.type !== "MemberExpression" || + rightNode.object.type === "Super" || + rightNode.property.type === "PrivateIdentifier" + ) { return; } diff --git a/eslint/lib/rules/prefer-exponentiation-operator.js b/eslint/lib/rules/prefer-exponentiation-operator.js index 6121af8..de802ce 100644 --- a/eslint/lib/rules/prefer-exponentiation-operator.js +++ b/eslint/lib/rules/prefer-exponentiation-operator.js @@ -90,7 +90,6 @@ module.exports = { docs: { description: "disallow the use of `Math.pow` in favor of the `**` operator", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/prefer-exponentiation-operator" }, diff --git a/eslint/lib/rules/prefer-named-capture-group.js b/eslint/lib/rules/prefer-named-capture-group.js index 7d0aa3f..41aa549 100644 --- a/eslint/lib/rules/prefer-named-capture-group.js +++ b/eslint/lib/rules/prefer-named-capture-group.js @@ -33,7 +33,6 @@ module.exports = { docs: { description: "enforce using named capture group in regular expression", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/prefer-named-capture-group" }, diff --git a/eslint/lib/rules/prefer-numeric-literals.js b/eslint/lib/rules/prefer-numeric-literals.js index cc82e66..91bb267 100644 --- a/eslint/lib/rules/prefer-numeric-literals.js +++ b/eslint/lib/rules/prefer-numeric-literals.js @@ -45,7 +45,6 @@ module.exports = { docs: { description: "disallow `parseInt()` and `Number.parseInt()` in favor of binary, octal, and hexadecimal literals", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/prefer-numeric-literals" }, diff --git a/eslint/lib/rules/prefer-object-spread.js b/eslint/lib/rules/prefer-object-spread.js index ab252c7..3958a51 100644 --- a/eslint/lib/rules/prefer-object-spread.js +++ b/eslint/lib/rules/prefer-object-spread.js @@ -247,7 +247,6 @@ module.exports = { docs: { description: "disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead.", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/prefer-object-spread" }, diff --git a/eslint/lib/rules/prefer-promise-reject-errors.js b/eslint/lib/rules/prefer-promise-reject-errors.js index ec16e44..bdc1fef 100644 --- a/eslint/lib/rules/prefer-promise-reject-errors.js +++ b/eslint/lib/rules/prefer-promise-reject-errors.js @@ -16,7 +16,6 @@ module.exports = { docs: { description: "require using Error objects as Promise rejection reasons", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/prefer-promise-reject-errors" }, diff --git a/eslint/lib/rules/prefer-reflect.js b/eslint/lib/rules/prefer-reflect.js index 156d612..fea88c6 100644 --- a/eslint/lib/rules/prefer-reflect.js +++ b/eslint/lib/rules/prefer-reflect.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "require `Reflect` methods where applicable", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/prefer-reflect" }, diff --git a/eslint/lib/rules/prefer-regex-literals.js b/eslint/lib/rules/prefer-regex-literals.js index 9e8ce02..fbfeb56 100644 --- a/eslint/lib/rules/prefer-regex-literals.js +++ b/eslint/lib/rules/prefer-regex-literals.js @@ -54,7 +54,6 @@ module.exports = { docs: { description: "disallow use of the `RegExp` constructor in favor of regular expression literals", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/prefer-regex-literals" }, diff --git a/eslint/lib/rules/prefer-rest-params.js b/eslint/lib/rules/prefer-rest-params.js index 3ecea73..157f0bb 100644 --- a/eslint/lib/rules/prefer-rest-params.js +++ b/eslint/lib/rules/prefer-rest-params.js @@ -65,7 +65,6 @@ module.exports = { docs: { description: "require rest parameters instead of `arguments`", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/prefer-rest-params" }, diff --git a/eslint/lib/rules/prefer-spread.js b/eslint/lib/rules/prefer-spread.js index d3c3c4d..3944fed 100644 --- a/eslint/lib/rules/prefer-spread.js +++ b/eslint/lib/rules/prefer-spread.js @@ -49,7 +49,6 @@ module.exports = { docs: { description: "require spread operators instead of `.apply()`", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/prefer-spread" }, diff --git a/eslint/lib/rules/prefer-template.js b/eslint/lib/rules/prefer-template.js index cb96766..564dd55 100644 --- a/eslint/lib/rules/prefer-template.js +++ b/eslint/lib/rules/prefer-template.js @@ -128,7 +128,6 @@ module.exports = { docs: { description: "require template literals instead of string concatenation", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/prefer-template" }, diff --git a/eslint/lib/rules/quote-props.js b/eslint/lib/rules/quote-props.js index fab7bdc..ce277cd 100644 --- a/eslint/lib/rules/quote-props.js +++ b/eslint/lib/rules/quote-props.js @@ -22,7 +22,6 @@ module.exports = { docs: { description: "require quotes around object literal property names", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/quote-props" }, @@ -91,7 +90,7 @@ module.exports = { /** * Checks whether a certain string constitutes an ES3 token - * @param {string} tokenStr The string to be checked. + * @param {string} tokenStr The string to be checked. * @returns {boolean} `true` if it is an ES3 token. */ function isKeyword(tokenStr) { @@ -100,9 +99,9 @@ module.exports = { /** * Checks if an espree-tokenized key has redundant quotes (i.e. whether quotes are unnecessary) - * @param {string} rawKey The raw key value from the source - * @param {espreeTokens} tokens The espree-tokenized node key - * @param {boolean} [skipNumberLiterals=false] Indicates whether number literals should be checked + * @param {string} rawKey The raw key value from the source + * @param {espreeTokens} tokens The espree-tokenized node key + * @param {boolean} [skipNumberLiterals=false] Indicates whether number literals should be checked * @returns {boolean} Whether or not a key has redundant quotes. * @private */ @@ -139,7 +138,7 @@ module.exports = { /** * Ensures that a property's key is quoted only when necessary - * @param {ASTNode} node Property AST node + * @param {ASTNode} node Property AST node * @returns {void} */ function checkUnnecessaryQuotes(node) { @@ -195,7 +194,7 @@ module.exports = { /** * Ensures that a property's key is quoted - * @param {ASTNode} node Property AST node + * @param {ASTNode} node Property AST node * @returns {void} */ function checkOmittedQuotes(node) { @@ -213,8 +212,8 @@ module.exports = { /** * Ensures that an object's keys are consistently quoted, optionally checks for redundancy of quotes - * @param {ASTNode} node Property AST node - * @param {boolean} checkQuotesRedundancy Whether to check quotes' redundancy + * @param {ASTNode} node Property AST node + * @param {boolean} checkQuotesRedundancy Whether to check quotes' redundancy * @returns {void} */ function checkConsistency(node, checkQuotesRedundancy) { diff --git a/eslint/lib/rules/quotes.js b/eslint/lib/rules/quotes.js index da7e127..d7959c0 100644 --- a/eslint/lib/rules/quotes.js +++ b/eslint/lib/rules/quotes.js @@ -80,7 +80,6 @@ module.exports = { docs: { description: "enforce the consistent use of either backticks, double, or single quotes", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/quotes" }, @@ -216,6 +215,7 @@ module.exports = { // LiteralPropertyName. case "Property": + case "PropertyDefinition": case "MethodDefinition": return parent.key === node && !parent.computed; diff --git a/eslint/lib/rules/radix.js b/eslint/lib/rules/radix.js index d1b67e5..0c6c6ff 100644 --- a/eslint/lib/rules/radix.js +++ b/eslint/lib/rules/radix.js @@ -80,12 +80,12 @@ module.exports = { docs: { description: "enforce the consistent use of the radix argument when using `parseInt()`", - category: "Best Practices", recommended: false, - url: "https://eslint.org/docs/rules/radix", - suggestion: true + url: "https://eslint.org/docs/rules/radix" }, + hasSuggestions: true, + schema: [ { enum: ["always", "as-needed"] diff --git a/eslint/lib/rules/require-atomic-updates.js b/eslint/lib/rules/require-atomic-updates.js index b3df907..248b0eb 100644 --- a/eslint/lib/rules/require-atomic-updates.js +++ b/eslint/lib/rules/require-atomic-updates.js @@ -79,6 +79,9 @@ function isLocalVariableWithoutEscape(variable, isMemberAccess) { reference.from.variableScope === functionScope); } +/** + * Represents segment information. + */ class SegmentInfo { constructor() { this.info = new WeakMap(); @@ -168,20 +171,32 @@ module.exports = { docs: { description: "disallow assignments that can lead to race conditions due to usage of `await` or `yield`", - category: "Possible Errors", recommended: false, url: "https://eslint.org/docs/rules/require-atomic-updates" }, fixable: null, - schema: [], + + schema: [{ + type: "object", + properties: { + allowProperties: { + type: "boolean", + default: false + } + }, + additionalProperties: false + }], messages: { - nonAtomicUpdate: "Possible race condition: `{{value}}` might be reassigned based on an outdated value of `{{value}}`." + nonAtomicUpdate: "Possible race condition: `{{value}}` might be reassigned based on an outdated value of `{{value}}`.", + nonAtomicObjectUpdate: "Possible race condition: `{{value}}` might be assigned based on an outdated state of `{{object}}`." } }, create(context) { + const allowProperties = !!context.options[0] && context.options[0].allowProperties; + const sourceCode = context.getSourceCode(); const assignmentReferences = new Map(); const segmentInfo = new SegmentInfo(); @@ -273,13 +288,25 @@ module.exports = { const variable = reference.resolved; if (segmentInfo.isOutdated(codePath.currentSegments, variable)) { - context.report({ - node: node.parent, - messageId: "nonAtomicUpdate", - data: { - value: sourceCode.getText(node.parent.left) - } - }); + if (node.parent.left === reference.identifier) { + context.report({ + node: node.parent, + messageId: "nonAtomicUpdate", + data: { + value: variable.name + } + }); + } else if (!allowProperties) { + context.report({ + node: node.parent, + messageId: "nonAtomicObjectUpdate", + data: { + value: sourceCode.getText(node.parent.left), + object: variable.name + } + }); + } + } } } diff --git a/eslint/lib/rules/require-await.js b/eslint/lib/rules/require-await.js index 9b5acc7..8ec6f54 100644 --- a/eslint/lib/rules/require-await.js +++ b/eslint/lib/rules/require-await.js @@ -34,7 +34,6 @@ module.exports = { docs: { description: "disallow async functions which have no `await` expression", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/require-await" }, diff --git a/eslint/lib/rules/require-jsdoc.js b/eslint/lib/rules/require-jsdoc.js index e581b2b..1d76e3d 100644 --- a/eslint/lib/rules/require-jsdoc.js +++ b/eslint/lib/rules/require-jsdoc.js @@ -1,6 +1,7 @@ /** * @fileoverview Rule to check for jsdoc presence. * @author Gyandeep Singh + * @deprecated in ESLint v5.10.0 */ "use strict"; @@ -10,7 +11,6 @@ module.exports = { docs: { description: "require JSDoc comments", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/require-jsdoc" }, diff --git a/eslint/lib/rules/require-unicode-regexp.js b/eslint/lib/rules/require-unicode-regexp.js index 880405e..a332b48 100644 --- a/eslint/lib/rules/require-unicode-regexp.js +++ b/eslint/lib/rules/require-unicode-regexp.js @@ -26,7 +26,6 @@ module.exports = { docs: { description: "enforce the use of `u` flag on RegExp", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/require-unicode-regexp" }, diff --git a/eslint/lib/rules/require-yield.js b/eslint/lib/rules/require-yield.js index af2344d..f5b5d53 100644 --- a/eslint/lib/rules/require-yield.js +++ b/eslint/lib/rules/require-yield.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "require generator functions to contain `yield`", - category: "ECMAScript 6", recommended: true, url: "https://eslint.org/docs/rules/require-yield" }, diff --git a/eslint/lib/rules/rest-spread-spacing.js b/eslint/lib/rules/rest-spread-spacing.js index 8cb9814..a636def 100644 --- a/eslint/lib/rules/rest-spread-spacing.js +++ b/eslint/lib/rules/rest-spread-spacing.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "enforce spacing between rest and spread operators and their expressions", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/rest-spread-spacing" }, diff --git a/eslint/lib/rules/semi-spacing.js b/eslint/lib/rules/semi-spacing.js index 5c546f2..e5e2ae2 100644 --- a/eslint/lib/rules/semi-spacing.js +++ b/eslint/lib/rules/semi-spacing.js @@ -17,7 +17,6 @@ module.exports = { docs: { description: "enforce consistent spacing before and after semicolons", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/semi-spacing" }, @@ -238,7 +237,8 @@ module.exports = { if (node.test) { checkSemicolonSpacing(sourceCode.getTokenAfter(node.test), node); } - } + }, + PropertyDefinition: checkNode }; } }; diff --git a/eslint/lib/rules/semi-style.js b/eslint/lib/rules/semi-style.js index 0c9bec4..2d17d02 100644 --- a/eslint/lib/rules/semi-style.js +++ b/eslint/lib/rules/semi-style.js @@ -15,19 +15,18 @@ const astUtils = require("./utils/ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -const SELECTOR = `:matches(${ - [ - "BreakStatement", "ContinueStatement", "DebuggerStatement", - "DoWhileStatement", "ExportAllDeclaration", - "ExportDefaultDeclaration", "ExportNamedDeclaration", - "ExpressionStatement", "ImportDeclaration", "ReturnStatement", - "ThrowStatement", "VariableDeclaration" - ].join(",") -})`; +const SELECTOR = [ + "BreakStatement", "ContinueStatement", "DebuggerStatement", + "DoWhileStatement", "ExportAllDeclaration", + "ExportDefaultDeclaration", "ExportNamedDeclaration", + "ExpressionStatement", "ImportDeclaration", "ReturnStatement", + "ThrowStatement", "VariableDeclaration", "PropertyDefinition" +].join(","); /** * Get the child node list of a given node. - * This returns `Program#body`, `BlockStatement#body`, or `SwitchCase#consequent`. + * This returns `BlockStatement#body`, `StaticBlock#body`, `Program#body`, + * `ClassBody#body`, or `SwitchCase#consequent`. * This is used to check whether a node is the first/last child. * @param {Node} node A node to get child node list. * @returns {Node[]|null} The child node list. @@ -35,7 +34,12 @@ const SELECTOR = `:matches(${ function getChildren(node) { const t = node.type; - if (t === "BlockStatement" || t === "Program") { + if ( + t === "BlockStatement" || + t === "StaticBlock" || + t === "Program" || + t === "ClassBody" + ) { return node.body; } if (t === "SwitchCase") { @@ -69,7 +73,6 @@ module.exports = { docs: { description: "enforce location of semicolons", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/semi-style" }, diff --git a/eslint/lib/rules/semi.js b/eslint/lib/rules/semi.js index d2f0670..c29029c 100644 --- a/eslint/lib/rules/semi.js +++ b/eslint/lib/rules/semi.js @@ -21,7 +21,6 @@ module.exports = { docs: { description: "require or disallow semicolons instead of ASI", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/semi" }, @@ -78,6 +77,8 @@ module.exports = { create(context) { const OPT_OUT_PATTERN = /^[-[(/+`]/u; // One of [(/+-` + const unsafeClassFieldNames = new Set(["get", "set", "static"]); + const unsafeClassFieldFollowers = new Set(["*", "in", "instanceof"]); const options = context.options[1]; const never = context.options[0] === "never"; const exceptOneLine = Boolean(options && options.omitLastInOneLineBlock); @@ -166,6 +167,55 @@ module.exports = { ); } + /** + * Checks if a given PropertyDefinition node followed by a semicolon + * can safely remove that semicolon. It is not to safe to remove if + * the class field name is "get", "set", or "static", or if + * followed by a generator method. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node cannot have the semicolon + * removed. + */ + function maybeClassFieldAsiHazard(node) { + + if (node.type !== "PropertyDefinition") { + return false; + } + + /* + * Computed property names and non-identifiers are always safe + * as they can be distinguished from keywords easily. + */ + const needsNameCheck = !node.computed && node.key.type === "Identifier"; + + /* + * Certain names are problematic unless they also have a + * a way to distinguish between keywords and property + * names. + */ + if (needsNameCheck && unsafeClassFieldNames.has(node.key.name)) { + + /* + * Special case: If the field name is `static`, + * it is only valid if the field is marked as static, + * so "static static" is okay but "static" is not. + */ + const isStaticStatic = node.static && node.key.name === "static"; + + /* + * For other unsafe names, we only care if there is no + * initializer. No initializer = hazard. + */ + if (!isStaticStatic && !node.value) { + return true; + } + } + + const followingToken = sourceCode.getTokenAfter(node); + + return unsafeClassFieldFollowers.has(followingToken.value); + } + /** * Check whether a given node is on the same line with the next token. * @param {Node} node A statement node to check. @@ -233,10 +283,19 @@ module.exports = { if (isRedundantSemi(sourceCode.getLastToken(node))) { return true; // `;;` or `;}` } + if (maybeClassFieldAsiHazard(node)) { + return false; + } if (isOnSameLineWithNextToken(node)) { return false; // One liner. } - if (beforeStatementContinuationChars === "never" && !maybeAsiHazardAfter(node)) { + + // continuation characters should not apply to class fields + if ( + node.type !== "PropertyDefinition" && + beforeStatementContinuationChars === "never" && + !maybeAsiHazardAfter(node) + ) { return true; // ASI works. This statement doesn't connect to the next. } if (!maybeAsiHazardBefore(sourceCode.getTokenAfter(node))) { @@ -247,22 +306,31 @@ module.exports = { } /** - * Checks a node to see if it's in a one-liner block statement. + * Checks a node to see if it's the last item in a one-liner block. + * Block is any `BlockStatement` or `StaticBlock` node. Block is a one-liner if its + * braces (and consequently everything between them) are on the same line. * @param {ASTNode} node The node to check. - * @returns {boolean} whether the node is in a one-liner block statement. + * @returns {boolean} whether the node is the last item in a one-liner block. */ - function isOneLinerBlock(node) { + function isLastInOneLinerBlock(node) { const parent = node.parent; const nextToken = sourceCode.getTokenAfter(node); if (!nextToken || nextToken.value !== "}") { return false; } - return ( - !!parent && - parent.type === "BlockStatement" && - parent.loc.start.line === parent.loc.end.line - ); + + if (parent.type === "BlockStatement") { + return parent.loc.start.line === parent.loc.end.line; + } + + if (parent.type === "StaticBlock") { + const openingBrace = sourceCode.getFirstToken(parent, { skip: 1 }); // skip the `static` token + + return openingBrace.loc.start.line === parent.loc.end.line; + } + + return false; } /** @@ -276,11 +344,15 @@ module.exports = { if (never) { if (isSemi && canRemoveSemicolon(node)) { report(node, true); - } else if (!isSemi && beforeStatementContinuationChars === "always" && maybeAsiHazardBefore(sourceCode.getTokenAfter(node))) { + } else if ( + !isSemi && beforeStatementContinuationChars === "always" && + node.type !== "PropertyDefinition" && + maybeAsiHazardBefore(sourceCode.getTokenAfter(node)) + ) { report(node); } } else { - const oneLinerBlock = (exceptOneLine && isOneLinerBlock(node)); + const oneLinerBlock = (exceptOneLine && isLastInOneLinerBlock(node)); if (isSemi && oneLinerBlock) { report(node, true); @@ -329,7 +401,8 @@ module.exports = { if (!/(?:Class|Function)Declaration/u.test(node.declaration.type)) { checkForSemicolon(node); } - } + }, + PropertyDefinition: checkForSemicolon }; } diff --git a/eslint/lib/rules/sort-imports.js b/eslint/lib/rules/sort-imports.js index 4c3ddec..dd43dad 100644 --- a/eslint/lib/rules/sort-imports.js +++ b/eslint/lib/rules/sort-imports.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "enforce sorted import declarations within modules", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/sort-imports" }, diff --git a/eslint/lib/rules/sort-keys.js b/eslint/lib/rules/sort-keys.js index 8a95ee2..65a9914 100644 --- a/eslint/lib/rules/sort-keys.js +++ b/eslint/lib/rules/sort-keys.js @@ -81,7 +81,6 @@ module.exports = { docs: { description: "require object keys to be sorted", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/sort-keys" }, diff --git a/eslint/lib/rules/sort-vars.js b/eslint/lib/rules/sort-vars.js index 7add2cf..0616c44 100644 --- a/eslint/lib/rules/sort-vars.js +++ b/eslint/lib/rules/sort-vars.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "require variables within the same declaration block to be sorted", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/sort-vars" }, diff --git a/eslint/lib/rules/space-before-blocks.js b/eslint/lib/rules/space-before-blocks.js index 87ef9bf..87d5b27 100644 --- a/eslint/lib/rules/space-before-blocks.js +++ b/eslint/lib/rules/space-before-blocks.js @@ -40,7 +40,6 @@ module.exports = { docs: { description: "enforce consistent spacing before blocks", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/space-before-blocks" }, @@ -108,13 +107,25 @@ module.exports = { * Checks whether the spacing before the given block is already controlled by another rule: * - `arrow-spacing` checks spaces after `=>`. * - `keyword-spacing` checks spaces after keywords in certain contexts. + * - `switch-colon-spacing` checks spaces after `:` of switch cases. * @param {Token} precedingToken first token before the block. * @param {ASTNode|Token} node `BlockStatement` node or `{` token of a `SwitchStatement` node. * @returns {boolean} `true` if requiring or disallowing spaces before the given block could produce conflicts with other rules. */ function isConflicted(precedingToken, node) { - return astUtils.isArrowToken(precedingToken) || - astUtils.isKeywordToken(precedingToken) && !isFunctionBody(node); + return ( + astUtils.isArrowToken(precedingToken) || + ( + astUtils.isKeywordToken(precedingToken) && + !isFunctionBody(node) + ) || + ( + astUtils.isColonToken(precedingToken) && + node.parent && + node.parent.type === "SwitchCase" && + precedingToken === astUtils.getSwitchCaseColonToken(node.parent, sourceCode) + ) + ); } /** diff --git a/eslint/lib/rules/space-before-function-paren.js b/eslint/lib/rules/space-before-function-paren.js index 1021a11..b60bee0 100644 --- a/eslint/lib/rules/space-before-function-paren.js +++ b/eslint/lib/rules/space-before-function-paren.js @@ -20,7 +20,6 @@ module.exports = { docs: { description: "enforce consistent spacing before `function` definition opening parenthesis", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/space-before-function-paren" }, diff --git a/eslint/lib/rules/space-in-parens.js b/eslint/lib/rules/space-in-parens.js index b0a604d..24378b8 100644 --- a/eslint/lib/rules/space-in-parens.js +++ b/eslint/lib/rules/space-in-parens.js @@ -16,7 +16,6 @@ module.exports = { docs: { description: "enforce consistent spacing inside parentheses", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/space-in-parens" }, diff --git a/eslint/lib/rules/space-infix-ops.js b/eslint/lib/rules/space-infix-ops.js index 3c55098..8065b52 100644 --- a/eslint/lib/rules/space-infix-ops.js +++ b/eslint/lib/rules/space-infix-ops.js @@ -4,6 +4,8 @@ */ "use strict"; +const { isEqToken } = require("./utils/ast-utils"); + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ @@ -14,7 +16,6 @@ module.exports = { docs: { description: "require spacing around infix operators", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/space-infix-ops" }, @@ -164,7 +165,29 @@ module.exports = { BinaryExpression: checkBinary, LogicalExpression: checkBinary, ConditionalExpression: checkConditional, - VariableDeclarator: checkVar + VariableDeclarator: checkVar, + + PropertyDefinition(node) { + if (!node.value) { + return; + } + + /* + * Because of computed properties and type annotations, some + * tokens may exist between `node.key` and `=`. + * Therefore, find the `=` from the right. + */ + const operatorToken = sourceCode.getTokenBefore(node.value, isEqToken); + const leftToken = sourceCode.getTokenBefore(operatorToken); + const rightToken = sourceCode.getTokenAfter(operatorToken); + + if ( + !sourceCode.isSpaceBetweenTokens(leftToken, operatorToken) || + !sourceCode.isSpaceBetweenTokens(operatorToken, rightToken) + ) { + report(node, operatorToken); + } + } }; } diff --git a/eslint/lib/rules/space-unary-ops.js b/eslint/lib/rules/space-unary-ops.js index 57f6e78..de9018f 100644 --- a/eslint/lib/rules/space-unary-ops.js +++ b/eslint/lib/rules/space-unary-ops.js @@ -20,7 +20,6 @@ module.exports = { docs: { description: "enforce consistent spacing before or after unary operators", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/space-unary-ops" }, diff --git a/eslint/lib/rules/spaced-comment.js b/eslint/lib/rules/spaced-comment.js index 226a2d4..6f0b432 100644 --- a/eslint/lib/rules/spaced-comment.js +++ b/eslint/lib/rules/spaced-comment.js @@ -152,7 +152,6 @@ module.exports = { docs: { description: "enforce consistent spacing after the `//` or `/*` in a comment", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/spaced-comment" }, diff --git a/eslint/lib/rules/strict.js b/eslint/lib/rules/strict.js index b0d6cf9..24af39d 100644 --- a/eslint/lib/rules/strict.js +++ b/eslint/lib/rules/strict.js @@ -69,7 +69,6 @@ module.exports = { docs: { description: "require or disallow strict mode directives", - category: "Strict Mode", recommended: false, url: "https://eslint.org/docs/rules/strict" }, diff --git a/eslint/lib/rules/switch-colon-spacing.js b/eslint/lib/rules/switch-colon-spacing.js index c906415..a4f3ba2 100644 --- a/eslint/lib/rules/switch-colon-spacing.js +++ b/eslint/lib/rules/switch-colon-spacing.js @@ -21,7 +21,6 @@ module.exports = { docs: { description: "enforce spacing around colons of switch statements", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/switch-colon-spacing" }, @@ -51,18 +50,6 @@ module.exports = { const beforeSpacing = options.before === true; // false by default const afterSpacing = options.after !== false; // true by default - /** - * Get the colon token of the given SwitchCase node. - * @param {ASTNode} node The SwitchCase node to get. - * @returns {Token} The colon token of the node. - */ - function getColonToken(node) { - if (node.test) { - return sourceCode.getTokenAfter(node.test, astUtils.isColonToken); - } - return sourceCode.getFirstToken(node, 1); - } - /** * Check whether the spacing between the given 2 tokens is valid or not. * @param {Token} left The left token to check. @@ -115,7 +102,7 @@ module.exports = { return { SwitchCase(node) { - const colonToken = getColonToken(node); + const colonToken = astUtils.getSwitchCaseColonToken(node, sourceCode); const beforeToken = sourceCode.getTokenBefore(colonToken); const afterToken = sourceCode.getTokenAfter(colonToken); diff --git a/eslint/lib/rules/symbol-description.js b/eslint/lib/rules/symbol-description.js index 155cea4..9f5d935 100644 --- a/eslint/lib/rules/symbol-description.js +++ b/eslint/lib/rules/symbol-description.js @@ -22,7 +22,6 @@ module.exports = { docs: { description: "require symbol descriptions", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/symbol-description" }, diff --git a/eslint/lib/rules/template-curly-spacing.js b/eslint/lib/rules/template-curly-spacing.js index 26043bc..5133a54 100644 --- a/eslint/lib/rules/template-curly-spacing.js +++ b/eslint/lib/rules/template-curly-spacing.js @@ -21,7 +21,6 @@ module.exports = { docs: { description: "require or disallow spacing around embedded expressions of template strings", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/template-curly-spacing" }, diff --git a/eslint/lib/rules/template-tag-spacing.js b/eslint/lib/rules/template-tag-spacing.js index 16f5862..45b6606 100644 --- a/eslint/lib/rules/template-tag-spacing.js +++ b/eslint/lib/rules/template-tag-spacing.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "require or disallow spacing between template tags and their literals", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/template-tag-spacing" }, diff --git a/eslint/lib/rules/unicode-bom.js b/eslint/lib/rules/unicode-bom.js index 39642f8..e80497d 100644 --- a/eslint/lib/rules/unicode-bom.js +++ b/eslint/lib/rules/unicode-bom.js @@ -14,7 +14,6 @@ module.exports = { docs: { description: "require or disallow Unicode byte order mark (BOM)", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/unicode-bom" }, diff --git a/eslint/lib/rules/use-isnan.js b/eslint/lib/rules/use-isnan.js index 0c7e888..cd8331f 100644 --- a/eslint/lib/rules/use-isnan.js +++ b/eslint/lib/rules/use-isnan.js @@ -21,7 +21,10 @@ const astUtils = require("./utils/ast-utils"); * @returns {boolean} `true` if the node is 'NaN' identifier. */ function isNaNIdentifier(node) { - return Boolean(node) && node.type === "Identifier" && node.name === "NaN"; + return Boolean(node) && ( + astUtils.isSpecificId(node, "NaN") || + astUtils.isSpecificMemberAccess(node, "Number", "NaN") + ); } //------------------------------------------------------------------------------ @@ -34,7 +37,6 @@ module.exports = { docs: { description: "require calls to `isNaN()` when checking for `NaN`", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/use-isnan" }, diff --git a/eslint/lib/rules/utils/ast-utils.js b/eslint/lib/rules/utils/ast-utils.js index 6b85300..16d7b81 100644 --- a/eslint/lib/rules/utils/ast-utils.js +++ b/eslint/lib/rules/utils/ast-utils.js @@ -35,7 +35,7 @@ const COMMENTS_IGNORE_PATTERN = /^\s*(?:eslint|jshint\s+|jslint\s+|istanbul\s+|g const LINEBREAKS = new Set(["\r\n", "\r", "\n", "\u2028", "\u2029"]); // A set of node types that can contain a list of statements -const STATEMENT_LIST_PARENTS = new Set(["Program", "BlockStatement", "SwitchCase"]); +const STATEMENT_LIST_PARENTS = new Set(["Program", "BlockStatement", "StaticBlock", "SwitchCase"]); const DECIMAL_INTEGER_PATTERN = /^(?:0|0[0-7]*[89]\d*|[1-9](?:_?\d)*)$/u; @@ -266,6 +266,7 @@ function getStaticPropertyName(node) { return getStaticPropertyName(node.expression); case "Property": + case "PropertyDefinition": case "MethodDefinition": prop = node.key; break; @@ -407,6 +408,7 @@ function isSameReference(left, right, disableStaticComputedKey = false) { return true; case "Identifier": + case "PrivateIdentifier": return left.name === right.name; case "Literal": return equalLiteralValue(left, right); @@ -516,6 +518,15 @@ function isParenthesised(sourceCode, node) { nextToken.value === ")" && nextToken.range[0] >= node.range[1]; } +/** + * Checks if the given token is a `=` token or not. + * @param {Token} token The token to check. + * @returns {boolean} `true` if the token is a `=` token. + */ +function isEqToken(token) { + return token.value === "=" && token.type === "Punctuator"; +} + /** * Checks if the given token is an arrow token or not. * @param {Token} token The token to check. @@ -649,6 +660,16 @@ function isKeywordToken(token) { * @returns {Token} `(` token. */ function getOpeningParenOfParams(node, sourceCode) { + + // If the node is an arrow function and doesn't have parens, this returns the identifier of the first param. + if (node.type === "ArrowFunctionExpression" && node.params.length === 1) { + const argToken = sourceCode.getFirstToken(node.params[0]); + const maybeParenToken = sourceCode.getTokenBefore(argToken); + + return isOpeningParenToken(maybeParenToken) ? maybeParenToken : argToken; + } + + // Otherwise, returns paren. return node.id ? sourceCode.getTokenAfter(node.id, isOpeningParenToken) : sourceCode.getFirstToken(node, isOpeningParenToken); @@ -735,6 +756,19 @@ function isLogicalAssignmentOperator(operator) { return LOGICAL_ASSIGNMENT_OPERATORS.has(operator); } +/** + * Get the colon token of the given SwitchCase node. + * @param {ASTNode} node The SwitchCase node to get. + * @param {SourceCode} sourceCode The source code object to get tokens. + * @returns {Token} The colon token of the node. + */ +function getSwitchCaseColonToken(node, sourceCode) { + if (node.test) { + return sourceCode.getTokenAfter(node.test, isColonToken); + } + return sourceCode.getFirstToken(node, 1); +} + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ @@ -794,6 +828,7 @@ module.exports = { isOpeningBracketToken, isOpeningParenToken, isSemicolonToken, + isEqToken, /** * Checks whether or not a given node is a string literal. @@ -836,8 +871,8 @@ module.exports = { /** * Validate that a string passed in is surrounded by the specified character - * @param {string} val The text to check. - * @param {string} character The character to see if it's surrounded by. + * @param {string} val The text to check. + * @param {string} character The character to see if it's surrounded by. * @returns {boolean} True if the text is surrounded by the character, false if not. * @private */ @@ -902,6 +937,8 @@ module.exports = { * * First, this checks the node: * + * - The given node is not in `PropertyDefinition#value` position. + * - The given node is not `StaticBlock`. * - The function name does not start with uppercase. It's a convention to capitalize the names * of constructor functions. This check is not performed if `capIsConstructor` is set to `false`. * - The function does not have a JSDoc comment that has a @this tag. @@ -916,13 +953,29 @@ module.exports = { * - The location is not on an ES2015 class. * - Its `bind`/`call`/`apply` method is not called directly. * - The function is not a callback of array methods (such as `.forEach()`) if `thisArg` is given. - * @param {ASTNode} node A function node to check. + * @param {ASTNode} node A function node to check. It also can be an implicit function, like `StaticBlock` + * or any expression that is `PropertyDefinition#value` node. * @param {SourceCode} sourceCode A SourceCode instance to get comments. * @param {boolean} [capIsConstructor = true] `false` disables the assumption that functions which name starts * with an uppercase or are assigned to a variable which name starts with an uppercase are constructors. * @returns {boolean} The function node is the default `this` binding. */ isDefaultThisBinding(node, sourceCode, { capIsConstructor = true } = {}) { + + /* + * Class field initializers are implicit functions, but ESTree doesn't have the AST node of field initializers. + * Therefore, A expression node at `PropertyDefinition#value` is a function. + * In this case, `this` is always not default binding. + */ + if (node.parent.type === "PropertyDefinition" && node.parent.value === node) { + return false; + } + + // Class static blocks are implicit functions. In this case, `this` is always not default binding. + if (node.type === "StaticBlock") { + return false; + } + if ( (capIsConstructor && isES5Constructor(node)) || hasJSDocThisTag(node, sourceCode) @@ -983,8 +1036,10 @@ module.exports = { * class A { get foo() { ... } } * class A { set foo() { ... } } * class A { static foo() { ... } } + * class A { foo = function() { ... } } */ case "Property": + case "PropertyDefinition": case "MethodDefinition": return parent.value !== currentNode; @@ -1261,7 +1316,8 @@ module.exports = { * 5e1_000 // false * 5n // false * 1_000n // false - * '5' // false + * "5" // false + * */ isDecimalInteger(node) { return node.type === "Literal" && typeof node.value === "number" && @@ -1323,6 +1379,16 @@ module.exports = { * - `class A { static async foo() {} }` .... `static async method 'foo'` * - `class A { static get foo() {} }` ...... `static getter 'foo'` * - `class A { static set foo(a) {} }` ..... `static setter 'foo'` + * - `class A { foo = () => {}; }` .......... `method 'foo'` + * - `class A { foo = function() {}; }` ..... `method 'foo'` + * - `class A { foo = function bar() {}; }` . `method 'foo'` + * - `class A { static foo = () => {}; }` ... `static method 'foo'` + * - `class A { '#foo' = () => {}; }` ....... `method '#foo'` + * - `class A { #foo = () => {}; }` ......... `private method #foo` + * - `class A { static #foo = () => {}; }` .. `static private method #foo` + * - `class A { '#foo'() {} }` .............. `method '#foo'` + * - `class A { #foo() {} }` ................ `private method #foo` + * - `class A { static #foo() {} }` ......... `static private method #foo` * @param {ASTNode} node The function node to get. * @returns {string} The name and kind of the function node. */ @@ -1330,8 +1396,15 @@ module.exports = { const parent = node.parent; const tokens = []; - if (parent.type === "MethodDefinition" && parent.static) { - tokens.push("static"); + if (parent.type === "MethodDefinition" || parent.type === "PropertyDefinition") { + + // The proposal uses `static` word consistently before visibility words: https://github.com/tc39/proposal-static-class-features + if (parent.static) { + tokens.push("static"); + } + if (!parent.computed && parent.key.type === "PrivateIdentifier") { + tokens.push("private"); + } } if (node.async) { tokens.push("async"); @@ -1340,9 +1413,7 @@ module.exports = { tokens.push("generator"); } - if (node.type === "ArrowFunctionExpression") { - tokens.push("arrow", "function"); - } else if (parent.type === "Property" || parent.type === "MethodDefinition") { + if (parent.type === "Property" || parent.type === "MethodDefinition") { if (parent.kind === "constructor") { return "constructor"; } @@ -1353,18 +1424,29 @@ module.exports = { } else { tokens.push("method"); } + } else if (parent.type === "PropertyDefinition") { + tokens.push("method"); } else { + if (node.type === "ArrowFunctionExpression") { + tokens.push("arrow"); + } tokens.push("function"); } - if (node.id) { - tokens.push(`'${node.id.name}'`); - } else { - const name = getStaticPropertyName(parent); + if (parent.type === "Property" || parent.type === "MethodDefinition" || parent.type === "PropertyDefinition") { + if (!parent.computed && parent.key.type === "PrivateIdentifier") { + tokens.push(`#${parent.key.name}`); + } else { + const name = getStaticPropertyName(parent); - if (name !== null) { - tokens.push(`'${name}'`); + if (name !== null) { + tokens.push(`'${name}'`); + } else if (node.id) { + tokens.push(`'${node.id.name}'`); + } } + } else if (node.id) { + tokens.push(`'${node.id.name}'`); } return tokens.join(" "); @@ -1457,6 +1539,12 @@ module.exports = { * ^^^^^^^^^^^^^^ * - `class A { static set foo(a) {} }` * ^^^^^^^^^^^^^^ + * - `class A { foo = function() {} }` + * ^^^^^^^^^^^^^^ + * - `class A { static foo = function() {} }` + * ^^^^^^^^^^^^^^^^^^^^^ + * - `class A { foo = (a, b) => {} }` + * ^^^^^^ * @param {ASTNode} node The function node to get. * @param {SourceCode} sourceCode The source code object to get tokens. * @returns {string} The location of the function node for reporting. @@ -1466,14 +1554,14 @@ module.exports = { let start = null; let end = null; - if (node.type === "ArrowFunctionExpression") { + if (parent.type === "Property" || parent.type === "MethodDefinition" || parent.type === "PropertyDefinition") { + start = parent.loc.start; + end = getOpeningParenOfParams(node, sourceCode).loc.start; + } else if (node.type === "ArrowFunctionExpression") { const arrowToken = sourceCode.getTokenBefore(node.body, isArrowToken); start = arrowToken.loc.start; end = arrowToken.loc.end; - } else if (parent.type === "Property" || parent.type === "MethodDefinition") { - start = parent.loc.start; - end = getOpeningParenOfParams(node, sourceCode).loc.start; } else { start = node.loc.start; end = getOpeningParenOfParams(node, sourceCode).loc.start; @@ -1573,9 +1661,9 @@ module.exports = { return sourceCode.getText().slice(leftToken.range[0], rightToken.range[1]); }, - /* + /** * Determine if a node has a possibility to be an Error object - * @param {ASTNode} node ASTNode to check + * @param {ASTNode} node ASTNode to check * @returns {boolean} True if there is a chance it contains an Error obj */ couldBeError(node) { @@ -1745,6 +1833,10 @@ module.exports = { return true; } + if (rightToken.type === "PrivateIdentifier") { + return true; + } + return false; }, @@ -1805,5 +1897,6 @@ module.exports = { isSpecificMemberAccess, equalLiteralValue, isSameReference, - isLogicalAssignmentOperator + isLogicalAssignmentOperator, + getSwitchCaseColonToken }; diff --git a/eslint/lib/rules/utils/lazy-loading-rule-map.js b/eslint/lib/rules/utils/lazy-loading-rule-map.js index d426d85..7f116a2 100644 --- a/eslint/lib/rules/utils/lazy-loading-rule-map.js +++ b/eslint/lib/rules/utils/lazy-loading-rule-map.js @@ -14,10 +14,10 @@ const debug = require("debug")("eslint:rules"); * const rules = new LazyLoadingRuleMap([ * ["eqeqeq", () => require("eqeqeq")], * ["semi", () => require("semi")], - * ["no-unused-vars", () => require("no-unused-vars")], - * ]) + * ["no-unused-vars", () => require("no-unused-vars")] + * ]); * - * rules.get("semi") // call `() => require("semi")` here. + * rules.get("semi"); // call `() => require("semi")` here. * * @extends {Map Rule>} */ diff --git a/eslint/lib/rules/valid-jsdoc.js b/eslint/lib/rules/valid-jsdoc.js index 9ec6938..824410b 100644 --- a/eslint/lib/rules/valid-jsdoc.js +++ b/eslint/lib/rules/valid-jsdoc.js @@ -1,6 +1,7 @@ /** * @fileoverview Validates JSDoc comments are syntactically correct * @author Nicholas C. Zakas + * @deprecated in ESLint v5.10.0 */ "use strict"; @@ -20,7 +21,6 @@ module.exports = { docs: { description: "enforce valid JSDoc comments", - category: "Possible Errors", recommended: false, url: "https://eslint.org/docs/rules/valid-jsdoc" }, diff --git a/eslint/lib/rules/valid-typeof.js b/eslint/lib/rules/valid-typeof.js index a0f20f7..33b64f5 100644 --- a/eslint/lib/rules/valid-typeof.js +++ b/eslint/lib/rules/valid-typeof.js @@ -14,7 +14,6 @@ module.exports = { docs: { description: "enforce comparing `typeof` expressions against valid strings", - category: "Possible Errors", recommended: true, url: "https://eslint.org/docs/rules/valid-typeof" }, diff --git a/eslint/lib/rules/vars-on-top.js b/eslint/lib/rules/vars-on-top.js index 28ddae4..0f95d58 100644 --- a/eslint/lib/rules/vars-on-top.js +++ b/eslint/lib/rules/vars-on-top.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "require `var` declarations be placed at the top of their containing scope", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/vars-on-top" }, @@ -32,8 +31,8 @@ module.exports = { // Helpers //-------------------------------------------------------------------------- - // eslint-disable-next-line jsdoc/require-description /** + * Has AST suggesting a directive. * @param {ASTNode} node any node * @returns {boolean} whether the given node structurally represents a directive */ @@ -78,10 +77,12 @@ module.exports = { const l = statements.length; let i = 0; - // skip over directives - for (; i < l; ++i) { - if (!looksLikeDirective(statements[i]) && !looksLikeImport(statements[i])) { - break; + // Skip over directives and imports. Static blocks don't have either. + if (node.parent.type !== "StaticBlock") { + for (; i < l; ++i) { + if (!looksLikeDirective(statements[i]) && !looksLikeImport(statements[i])) { + break; + } } } @@ -112,16 +113,27 @@ module.exports = { /** * Checks whether variable is on top at functional block scope level * @param {ASTNode} node The node to check - * @param {ASTNode} parent Parent of the node - * @param {ASTNode} grandParent Parent of the node's parent * @returns {void} */ - function blockScopeVarCheck(node, parent, grandParent) { - if (!(/Function/u.test(grandParent.type) && - parent.type === "BlockStatement" && - isVarOnTop(node, parent.body))) { - context.report({ node, messageId: "top" }); + function blockScopeVarCheck(node) { + const { parent } = node; + + if ( + parent.type === "BlockStatement" && + /Function/u.test(parent.parent.type) && + isVarOnTop(node, parent.body) + ) { + return; } + + if ( + parent.type === "StaticBlock" && + isVarOnTop(node, parent.body) + ) { + return; + } + + context.report({ node, messageId: "top" }); } //-------------------------------------------------------------------------- @@ -135,7 +147,7 @@ module.exports = { } else if (node.parent.type === "Program") { globalVarCheck(node, node.parent); } else { - blockScopeVarCheck(node, node.parent, node.parent.parent); + blockScopeVarCheck(node); } } }; diff --git a/eslint/lib/rules/wrap-iife.js b/eslint/lib/rules/wrap-iife.js index 07e5b84..498d7bd 100644 --- a/eslint/lib/rules/wrap-iife.js +++ b/eslint/lib/rules/wrap-iife.js @@ -43,7 +43,6 @@ module.exports = { docs: { description: "require parentheses around immediate `function` invocations", - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/wrap-iife" }, diff --git a/eslint/lib/rules/wrap-regex.js b/eslint/lib/rules/wrap-regex.js index 4ecbcec..945eb5e 100644 --- a/eslint/lib/rules/wrap-regex.js +++ b/eslint/lib/rules/wrap-regex.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "require parenthesis around regex literals", - category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/wrap-regex" }, diff --git a/eslint/lib/rules/yield-star-spacing.js b/eslint/lib/rules/yield-star-spacing.js index 20b8e9e..8c3eefa 100644 --- a/eslint/lib/rules/yield-star-spacing.js +++ b/eslint/lib/rules/yield-star-spacing.js @@ -15,7 +15,6 @@ module.exports = { docs: { description: "require or disallow spacing around the `*` in `yield*` expressions", - category: "ECMAScript 6", recommended: false, url: "https://eslint.org/docs/rules/yield-star-spacing" }, diff --git a/eslint/lib/rules/yoda.js b/eslint/lib/rules/yoda.js index 2fca757..2d018dc 100644 --- a/eslint/lib/rules/yoda.js +++ b/eslint/lib/rules/yoda.js @@ -121,7 +121,6 @@ module.exports = { docs: { description: 'require or disallow "Yoda" conditions', - category: "Best Practices", recommended: false, url: "https://eslint.org/docs/rules/yoda" }, diff --git a/eslint/lib/shared/ajv.js b/eslint/lib/shared/ajv.js index 3fb0fbd..f4f6a62 100644 --- a/eslint/lib/shared/ajv.js +++ b/eslint/lib/shared/ajv.js @@ -27,7 +27,7 @@ module.exports = (additionalOptions = {}) => { }); ajv.addMetaSchema(metaSchema); - // eslint-disable-next-line no-underscore-dangle + // eslint-disable-next-line no-underscore-dangle -- Ajv's API ajv._opts.defaultMeta = metaSchema.id; return ajv; diff --git a/eslint/lib/shared/config-validator.js b/eslint/lib/shared/config-validator.js index 03b32f1..47353ac 100644 --- a/eslint/lib/shared/config-validator.js +++ b/eslint/lib/shared/config-validator.js @@ -24,9 +24,13 @@ const util = require("util"), configSchema = require("../../conf/config-schema"), - BuiltInEnvironments = require("@eslint/eslintrc/conf/environments"), BuiltInRules = require("../rules"), - ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"), + { + Legacy: { + ConfigOps, + environments: BuiltInEnvironments + } + } = require("@eslint/eslintrc"), { emitDeprecationWarning } = require("./deprecation-warnings"); const ajv = require("./ajv")(); @@ -80,6 +84,7 @@ function getRuleOptionsSchema(rule) { /** * Validates a rule's severity and returns the severity value. Throws an error if the severity is invalid. * @param {options} options The given options for the rule. + * @throws {Error} Wrong severity value. * @returns {number|string} The rule's severity value */ function validateRuleSeverity(options) { @@ -98,6 +103,7 @@ function validateRuleSeverity(options) { * Validates the non-severity options passed to a rule, based on its schema. * @param {{create: Function}} rule The rule to validate * @param {Array} localOptions The options for the rule, excluding severity + * @throws {Error} Any rule validation errors. * @returns {void} */ function validateRuleSchema(rule, localOptions) { @@ -128,6 +134,7 @@ function validateRuleSchema(rule, localOptions) { * @param {Array|number} options The given options for the rule. * @param {string|null} source The name of the configuration source to report in any errors. If null or undefined, * no source is prepended to the message. + * @throws {Error} Upon any bad rule configuration. * @returns {void} */ function validateRuleOptions(rule, ruleId, options, source = null) { @@ -152,7 +159,7 @@ function validateRuleOptions(rule, ruleId, options, source = null) { * Validates an environment object * @param {Object} environment The environment config object to validate. * @param {string} source The name of the configuration source to report in any errors. - * @param {function(envId:string): Object} [getAdditionalEnv] A map from strings to loaded environments. + * @param {(envId:string) => Object} [getAdditionalEnv] A map from strings to loaded environments. * @returns {void} */ function validateEnvironment( @@ -181,7 +188,7 @@ function validateEnvironment( * Validates a rules config object * @param {Object} rulesConfig The rules config object to validate. * @param {string} source The name of the configuration source to report in any errors. - * @param {function(ruleId:string): Object} getAdditionalRule A map from strings to loaded rules + * @param {(ruleId:string) => Object} getAdditionalRule A map from strings to loaded rules * @returns {void} */ function validateRules( @@ -225,7 +232,8 @@ function validateGlobals(globalsConfig, source = null) { * Validate `processor` configuration. * @param {string|undefined} processorName The processor name. * @param {string} source The name of config file. - * @param {function(id:string): Processor} getProcessor The getter of defined processors. + * @param {(id:string) => Processor} getProcessor The getter of defined processors. + * @throws {Error} For invalid processor configuration. * @returns {void} */ function validateProcessor(processorName, source, getProcessor) { @@ -264,6 +272,7 @@ function formatErrors(errors) { * Validates the top level properties of the config object. * @param {Object} config The config object to validate. * @param {string} source The name of the configuration source to report in any errors. + * @throws {Error} For any config invalid per the schema. * @returns {void} */ function validateConfigSchema(config, source = null) { @@ -282,8 +291,8 @@ function validateConfigSchema(config, source = null) { * Validates an entire config object. * @param {Object} config The config object to validate. * @param {string} source The name of the configuration source to report in any errors. - * @param {function(ruleId:string): Object} [getAdditionalRule] A map from strings to loaded rules. - * @param {function(envId:string): Object} [getAdditionalEnv] A map from strings to loaded envs. + * @param {(ruleId:string) => Object} [getAdditionalRule] A map from strings to loaded rules. + * @param {(envId:string) => Object} [getAdditionalEnv] A map from strings to loaded envs. * @returns {void} */ function validate(config, source, getAdditionalRule, getAdditionalEnv) { diff --git a/eslint/lib/shared/logging.js b/eslint/lib/shared/logging.js index 6aa1f5a..7f06a74 100644 --- a/eslint/lib/shared/logging.js +++ b/eslint/lib/shared/logging.js @@ -5,7 +5,7 @@ "use strict"; -/* eslint no-console: "off" */ +/* eslint no-console: "off" -- Logging util */ /* istanbul ignore next */ module.exports = { diff --git a/eslint/lib/shared/relative-module-resolver.js b/eslint/lib/shared/relative-module-resolver.js index cd743f3..18a6949 100644 --- a/eslint/lib/shared/relative-module-resolver.js +++ b/eslint/lib/shared/relative-module-resolver.js @@ -17,14 +17,7 @@ "use strict"; -const Module = require("module"); - -/* - * `Module.createRequire` is added in v12.2.0. It supports URL as well. - * We only support the case where the argument is a filepath, not a URL. - */ -// eslint-disable-next-line node/no-unsupported-features/node-builtins, node/no-deprecated-api -const createRequire = Module.createRequire || Module.createRequireFromPath; +const { createRequire } = require("module"); module.exports = { @@ -33,6 +26,7 @@ module.exports = { * @param {string} moduleName The name of a Node module, or a path to a Node module. * @param {string} relativeToPath An absolute path indicating the module that `moduleName` should be resolved relative to. This must be * a file rather than a directory, but the file need not actually exist. + * @throws {Error} Any error from `module.createRequire` or its `resolve`. * @returns {string} The absolute path that would result from calling `require.resolve(moduleName)` in a file located at `relativeToPath` */ resolve(moduleName, relativeToPath) { diff --git a/eslint/lib/shared/runtime-info.js b/eslint/lib/shared/runtime-info.js index aa5eff7..56c0898 100644 --- a/eslint/lib/shared/runtime-info.js +++ b/eslint/lib/shared/runtime-info.js @@ -40,6 +40,7 @@ function environment() { * Synchronously executes a shell command and formats the result. * @param {string} cmd The command to execute. * @param {Array} args The arguments to be executed with the command. + * @throws {Error} As may be collected by `cross-spawn.sync`. * @returns {string} The version returned by the command. */ function execCommand(cmd, args) { @@ -73,6 +74,7 @@ function environment() { /** * Gets bin version. * @param {string} bin The bin to check. + * @throws {Error} As may be collected by `cross-spawn.sync`. * @returns {string} The normalized version returned by the command. */ function getBinVersion(bin) { @@ -90,6 +92,7 @@ function environment() { * Gets installed npm package version. * @param {string} pkg The package to check. * @param {boolean} global Whether to check globally or not. + * @throws {Error} As may be collected by `cross-spawn.sync`. * @returns {string} The normalized version returned by the command. */ function getNpmPackageVersion(pkg, { global = false } = {}) { diff --git a/eslint/lib/shared/traverser.js b/eslint/lib/shared/traverser.js index 32f7677..be0ed59 100644 --- a/eslint/lib/shared/traverser.js +++ b/eslint/lib/shared/traverser.js @@ -65,16 +65,16 @@ class Traverser { this._leave = null; } - // eslint-disable-next-line jsdoc/require-description /** + * Gives current node. * @returns {ASTNode} The current node. */ current() { return this._current; } - // eslint-disable-next-line jsdoc/require-description /** + * Gives a a copy of the ancestor nodes. * @returns {ASTNode[]} The ancestor nodes. */ parents() { diff --git a/eslint/lib/shared/types.js b/eslint/lib/shared/types.js index c3b76e4..c497f78 100644 --- a/eslint/lib/shared/types.js +++ b/eslint/lib/shared/types.js @@ -21,7 +21,7 @@ module.exports = {}; /** * @typedef {Object} ParserOptions * @property {EcmaFeatures} [ecmaFeatures] The optional features. - * @property {3|5|6|7|8|9|10|11|12|2015|2016|2017|2018|2019|2020|2021} [ecmaVersion] The ECMAScript version (or revision number). + * @property {3|5|6|7|8|9|10|11|12|13|2015|2016|2017|2018|2019|2020|2021|2022} [ecmaVersion] The ECMAScript version (or revision number). * @property {"script"|"module"} [sourceType] The source code type. */ @@ -83,12 +83,12 @@ module.exports = {}; /** * @typedef {Object} LintMessage - * @property {number} column The 1-based column number. + * @property {number|undefined} column The 1-based column number. * @property {number} [endColumn] The 1-based column number of the end location. * @property {number} [endLine] The 1-based line number of the end location. * @property {boolean} fatal If `true` then this is a fatal error. * @property {{range:[number,number], text:string}} [fix] Information for autofix. - * @property {number} line The 1-based line number. + * @property {number|undefined} line The 1-based line number. * @property {string} message The error message. * @property {string|null} ruleId The ID of the rule which makes this message. * @property {0|1|2} severity The severity of this message. diff --git a/eslint/lib/source-code/source-code.js b/eslint/lib/source-code/source-code.js index c13ce29..49b2820 100644 --- a/eslint/lib/source-code/source-code.js +++ b/eslint/lib/source-code/source-code.js @@ -143,10 +143,12 @@ function isSpaceBetween(sourceCode, first, second, checkInsideOfJSXText) { // Public Interface //------------------------------------------------------------------------------ +/** + * Represents parsed source code. + */ class SourceCode extends TokenStore { /** - * Represents parsed source code. * @param {string|Object} textOrConfig The source code text or config object. * @param {string} textOrConfig.text The source code text. * @param {ASTNode} textOrConfig.ast The Program node of the AST representing the code. This AST should be created from the text that BOM was stripped. @@ -175,20 +177,20 @@ class SourceCode extends TokenStore { /** * The flag to indicate that the source code has Unicode BOM. - * @type boolean + * @type {boolean} */ this.hasBOM = (text.charCodeAt(0) === 0xFEFF); /** * The original text source code. * BOM was stripped from this text. - * @type string + * @type {string} */ this.text = (this.hasBOM ? text.slice(1) : text); /** * The parsed AST for the source code. - * @type ASTNode + * @type {ASTNode} */ this.ast = ast; @@ -223,7 +225,7 @@ class SourceCode extends TokenStore { /** * The source code split into lines according to ECMA-262 specification. * This is done to avoid each rule needing to do so separately. - * @type string[] + * @type {string[]} */ this.lines = []; this.lineStartIndices = [0]; @@ -349,7 +351,7 @@ class SourceCode extends TokenStore { let currentToken = this.getTokenBefore(node, { includeComments: true }); while (currentToken && isCommentToken(currentToken)) { - if (node.parent && (currentToken.start < node.parent.start)) { + if (node.parent && node.parent.type !== "Program" && (currentToken.start < node.parent.start)) { break; } comments.leading.push(currentToken); @@ -361,7 +363,7 @@ class SourceCode extends TokenStore { currentToken = this.getTokenAfter(node, { includeComments: true }); while (currentToken && isCommentToken(currentToken)) { - if (node.parent && (currentToken.end > node.parent.end)) { + if (node.parent && node.parent.type !== "Program" && (currentToken.end > node.parent.end)) { break; } comments.trailing.push(currentToken); @@ -506,6 +508,7 @@ class SourceCode extends TokenStore { /** * Converts a source text index into a (line, column) pair. * @param {number} index The index of a character in a file + * @throws {TypeError} If non-numeric index or index out of range. * @returns {Object} A {line, column} location object with a 0-indexed column * @public */ @@ -545,6 +548,9 @@ class SourceCode extends TokenStore { * @param {Object} loc A line/column location * @param {number} loc.line The line number of the location (1-indexed) * @param {number} loc.column The column number of the location (0-indexed) + * @throws {TypeError|RangeError} If `loc` is not an object with a numeric + * `line` and `column`, if the `line` is less than or equal to zero or + * the line or column is out of the expected range. * @returns {number} The range index of the location in the file. * @public */ diff --git a/eslint/lib/source-code/token-store/cursor.js b/eslint/lib/source-code/token-store/cursor.js index 4e1595c..494762a 100644 --- a/eslint/lib/source-code/token-store/cursor.js +++ b/eslint/lib/source-code/token-store/cursor.js @@ -70,7 +70,7 @@ module.exports = class Cursor { * @abstract */ /* istanbul ignore next */ - moveNext() { // eslint-disable-line class-methods-use-this + moveNext() { // eslint-disable-line class-methods-use-this -- Unused throw new Error("Not implemented."); } }; diff --git a/eslint/lib/unsupported-api.js b/eslint/lib/unsupported-api.js new file mode 100644 index 0000000..110b35a --- /dev/null +++ b/eslint/lib/unsupported-api.js @@ -0,0 +1,23 @@ +/** + * @fileoverview APIs that are not officially supported by ESLint. + * These APIs may change or be removed at any time. Use at your + * own risk. + * @author Nicholas C. Zakas + */ + +"use strict"; + +//----------------------------------------------------------------------------- +// Requirements +//----------------------------------------------------------------------------- + +const { FileEnumerator } = require("./cli-engine/file-enumerator"); + +//----------------------------------------------------------------------------- +// Exports +//----------------------------------------------------------------------------- + +module.exports = { + builtinRules: require("./rules"), + FileEnumerator +}; diff --git a/eslint/package.json b/eslint/package.json index 601033e..c3dfa1b 100644 --- a/eslint/package.json +++ b/eslint/package.json @@ -1,12 +1,17 @@ { "name": "eslint", - "version": "7.28.0", + "version": "8.3.0", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "bin": { "eslint": "./bin/eslint.js" }, "main": "./lib/api.js", + "exports": { + "./package.json": "./package.json", + ".": "./lib/api.js", + "./use-at-your-own-risk": "./lib/unsupported-api.js" + }, "scripts": { "test": "node Makefile.js test", "test:cli": "mocha", @@ -18,7 +23,6 @@ "generate-betarelease": "node Makefile.js generatePrerelease -- beta", "generate-rcrelease": "node Makefile.js generatePrerelease -- rc", "publish-release": "node Makefile.js publishRelease", - "docs": "node Makefile.js docs", "gensite": "node Makefile.js gensite", "webpack": "node Makefile.js webpack", "perf": "node Makefile.js perf" @@ -43,31 +47,31 @@ "homepage": "https://eslint.org", "bugs": "https://github.com/eslint/eslint/issues/", "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.2", + "@eslint/eslintrc": "^1.0.4", + "@humanwhocodes/config-array": "^0.6.0", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", - "debug": "^4.0.1", + "debug": "^4.3.2", "doctrine": "^3.0.0", "enquirer": "^2.3.5", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", + "eslint-scope": "^7.1.0", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.1.0", + "espree": "^9.1.0", "esquery": "^1.4.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", + "glob-parent": "^6.0.1", "globals": "^13.6.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", + "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", @@ -75,11 +79,10 @@ "natural-compare": "^1.4.0", "optionator": "^0.9.1", "progress": "^2.0.0", - "regexpp": "^3.1.0", + "regexpp": "^3.2.0", "semver": "^7.2.1", - "strip-ansi": "^6.0.0", + "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", - "table": "^6.0.9", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, @@ -91,18 +94,19 @@ "cheerio": "^0.22.0", "common-tags": "^1.8.0", "core-js": "^3.1.3", - "dateformat": "^3.0.3", + "dateformat": "^4.5.1", "ejs": "^3.0.2", "eslint": "file:.", "eslint-config-eslint": "file:packages/eslint-config-eslint", - "eslint-plugin-eslint-plugin": "^3.0.3", + "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-eslint-plugin": "^4.0.1", "eslint-plugin-internal-rules": "file:tools/internal-rules", - "eslint-plugin-jsdoc": "^25.4.3", + "eslint-plugin-jsdoc": "^37.0.0", "eslint-plugin-node": "^11.1.0", - "eslint-release": "^2.0.0", + "eslint-release": "^3.2.0", "eslump": "^3.0.0", "esprima": "^4.0.1", - "fs-teardown": "^0.1.0", + "fs-teardown": "^0.1.3", "glob": "^7.1.6", "jsdoc": "^3.5.5", "karma": "^6.1.1", @@ -110,10 +114,10 @@ "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", "karma-webpack": "^5.0.0", - "lint-staged": "^10.1.2", + "lint-staged": "^11.0.0", "load-perf": "^0.2.0", - "markdownlint": "^0.19.0", - "markdownlint-cli": "^0.22.0", + "markdownlint": "^0.23.1", + "markdownlint-cli": "^0.28.1", "memfs": "^3.0.1", "mocha": "^8.3.2", "mocha-junit-reporter": "^2.0.0", @@ -121,11 +125,11 @@ "npm-license": "^0.3.3", "nyc": "^15.0.1", "proxyquire": "^2.0.1", - "puppeteer": "^7.1.0", - "recast": "^0.19.0", + "puppeteer": "^9.1.1", + "recast": "^0.20.4", "regenerator-runtime": "^0.13.2", "shelljs": "^0.8.2", - "sinon": "^9.0.1", + "sinon": "^11.0.0", "temp": "^0.9.0", "webpack": "^5.23.0", "webpack-cli": "^4.5.0", @@ -140,6 +144,6 @@ ], "license": "MIT", "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } } diff --git a/eslint/templates/blogpost.md.ejs b/eslint/templates/blogpost.md.ejs index a812a79..71e1973 100644 --- a/eslint/templates/blogpost.md.ejs +++ b/eslint/templates/blogpost.md.ejs @@ -5,7 +5,6 @@ tags: - release - <%- type %> --- -# ESLint v<%= version %> released We just pushed ESLint v<%- version %>, which is a <%- type %> release upgrade of ESLint. This release <% if (type !== "patch") { %>adds some new features and <% } %>fixes several bugs found in the previous release.<% if (type === "major") { %> This release also has some breaking changes, so please read the following closely.<% } %> diff --git a/eslint/templates/formatter-examples.md.ejs b/eslint/templates/formatter-examples.md.ejs index 3231ea4..b558395 100644 --- a/eslint/templates/formatter-examples.md.ejs +++ b/eslint/templates/formatter-examples.md.ejs @@ -6,7 +6,7 @@ layout: doc ESLint comes with several built-in formatters to control the appearance of the linting results, and supports third-party formatters as well. -You can specify a formatter using the `--format` or `-f` flag on the command line. For example, `--format codeframe` uses the `codeframe` formatter. +You can specify a formatter using the `--format` or `-f` flag on the command line. For example, `--format json` uses the `json` formatter. The built-in formatter options are: diff --git a/eslint/tests/_utils/in-memory-fs.js b/eslint/tests/_utils/in-memory-fs.js index ddacfc7..8242096 100644 --- a/eslint/tests/_utils/in-memory-fs.js +++ b/eslint/tests/_utils/in-memory-fs.js @@ -1,244 +1,19 @@ /** - * @fileoverview Define classes what use the in-memory file system. - * - * This provides utilities to test `ConfigArrayFactory`, - * `CascadingConfigArrayFactory`, `FileEnumerator`, `CLIEngine`, and `ESLint`. - * - * - `defineConfigArrayFactoryWithInMemoryFileSystem({ cwd, files })` - * - `defineCascadingConfigArrayFactoryWithInMemoryFileSystem({ cwd, files })` - * - `defineFileEnumeratorWithInMemoryFileSystem({ cwd, files })` - * - `defineCLIEngineWithInMemoryFileSystem({ cwd, files })` - * - `defineESLintWithInMemoryFileSystem({ cwd, files })` - * - * Those functions define correspond classes with the in-memory file system. - * Those search config files, parsers, and plugins in the `files` option via the - * in-memory file system. - * - * For each test case, it makes more readable if we define minimal files the - * test case requires. - * - * For example: - * - * ```js - * const { ConfigArrayFactory } = defineConfigArrayFactoryWithInMemoryFileSystem({ - * files: { - * "node_modules/eslint-config-foo/index.js": ` - * module.exports = { - * parser: "./parser", - * rules: { - * "no-undef": "error" - * } - * } - * `, - * "node_modules/eslint-config-foo/parser.js": ` - * module.exports = { - * parse() {} - * } - * `, - * ".eslintrc.json": JSON.stringify({ root: true, extends: "foo" }) - * } - * }); - * const factory = new ConfigArrayFactory(); - * const config = factory.loadFile(".eslintrc.json"); - * - * assert(config[0].name === ".eslintrc.json » eslint-config-foo"); - * assert(config[0].filePath === path.resolve("node_modules/eslint-config-foo/index.js")); - * assert(config[0].parser.filePath === path.resolve("node_modules/eslint-config-foo/parser.js")); - * - * assert(config[1].name === ".eslintrc.json"); - * assert(config[1].filePath === path.resolve(".eslintrc.json")); - * assert(config[1].root === true); - * ``` - * + * @fileoverview in-memory file system. * @author Toru Nagashima */ "use strict"; +//----------------------------------------------------------------------------- +// Requirements +//----------------------------------------------------------------------------- + const path = require("path"); -const vm = require("vm"); const { Volume, createFsFromVolume } = require("memfs"); -const Proxyquire = require("proxyquire/lib/proxyquire"); - -const CascadingConfigArrayFactoryPath = - require.resolve("@eslint/eslintrc/lib/cascading-config-array-factory"); -const CLIEnginePath = - require.resolve("../../lib/cli-engine/cli-engine"); -const ConfigArrayFactoryPath = - require.resolve("@eslint/eslintrc/lib/config-array-factory"); -const FileEnumeratorPath = - require.resolve("../../lib/cli-engine/file-enumerator"); -const LoadRulesPath = - require.resolve("../../lib/cli-engine/load-rules"); -const ESLintPath = - require.resolve("../../lib/eslint/eslint"); -const ESLintAllPath = - require.resolve("../../conf/eslint-all"); -const ESLintRecommendedPath = - require.resolve("../../conf/eslint-recommended"); - -// Ensure the needed files has been loaded and cached. -require(CascadingConfigArrayFactoryPath); -require(CLIEnginePath); -require(ConfigArrayFactoryPath); -require(FileEnumeratorPath); -require(LoadRulesPath); -require(ESLintPath); -require("js-yaml"); -require("espree"); - -// Override `_require` in order to throw runtime errors in stubs. -const ERRORED = Symbol("errored"); -const proxyquire = new class extends Proxyquire { - _require(...args) { - const retv = super._require(...args); // eslint-disable-line no-underscore-dangle - - if (retv[ERRORED]) { - throw retv[ERRORED]; - } - return retv; - } -}(module).noCallThru().noPreserveCache(); - -// Separated (sandbox) context to compile fixture files. -const context = vm.createContext(); - -/** - * Check if a given path is an existing file. - * @param {import("fs")} fs The file system. - * @param {string} filePath Tha path to a file to check. - * @returns {boolean} `true` if the file existed. - */ -function isExistingFile(fs, filePath) { - try { - return fs.statSync(filePath).isFile(); - } catch { - return false; - } -} - -/** - * Get some paths to test. - * @param {string} prefix The prefix to try. - * @returns {string[]} The paths to test. - */ -function getTestPaths(prefix) { - return [ - path.join(prefix), - path.join(`${prefix}.js`), - path.join(prefix, "index.js") - ]; -} - -/** - * Iterate the candidate paths of a given request to resolve. - * @param {string} request Tha package name or file path to resolve. - * @param {string} relativeTo Tha path to the file what called this resolving. - * @returns {IterableIterator} The candidate paths. - */ -function *iterateCandidatePaths(request, relativeTo) { - if (path.isAbsolute(request)) { - yield* getTestPaths(request); - return; - } - if (/^\.{1,2}[/\\]/u.test(request)) { - yield* getTestPaths(path.resolve(path.dirname(relativeTo), request)); - return; - } - - let prevPath = path.resolve(relativeTo); - let dirPath = path.dirname(prevPath); - - while (dirPath && dirPath !== prevPath) { - yield* getTestPaths(path.join(dirPath, "node_modules", request)); - prevPath = dirPath; - dirPath = path.dirname(dirPath); - } -} - -/** - * Resolve a given module name or file path relatively in the given file system. - * @param {import("fs")} fs The file system. - * @param {string} request Tha package name or file path to resolve. - * @param {string} relativeTo Tha path to the file what called this resolving. - * @returns {void} - */ -function fsResolve(fs, request, relativeTo) { - for (const filePath of iterateCandidatePaths(request, relativeTo)) { - if (isExistingFile(fs, filePath)) { - return filePath; - } - } - throw Object.assign( - new Error(`Cannot find module '${request}'`), - { code: "MODULE_NOT_FOUND" } - ); -} - -/** - * Compile a JavaScript file. - * This is used to compile only fixture files, so this is minimam. - * @param {import("fs")} fs The file system. - * @param {Object} stubs The stubs. - * @param {string} filePath The path to a JavaScript file to compile. - * @param {string} content The source code to compile. - * @returns {any} The exported value. - */ -function compile(fs, stubs, filePath, content) { - const code = `(function(exports, require, module, __filename, __dirname) { ${content} })`; - const f = vm.runInContext(code, context); - const exports = {}; - const module = { exports }; - - f.call( - exports, - exports, - request => { - const modulePath = fsResolve(fs, request, filePath); - const stub = stubs[modulePath]; - - if (stub[ERRORED]) { - throw stub[ERRORED]; - } - return stub; - }, - module, - filePath, - path.dirname(filePath) - ); - - return module.exports; -} - -/** - * Import a given file path in the given file system. - * @param {import("fs")} fs The file system. - * @param {Object} stubs The stubs. - * @param {string} absolutePath Tha file path to import. - * @returns {void} - */ -function fsImportFresh(fs, stubs, absolutePath) { - if (absolutePath === ESLintAllPath) { - return require(ESLintAllPath); - } - if (absolutePath === ESLintRecommendedPath) { - return require(ESLintRecommendedPath); - } - - if (fs.existsSync(absolutePath)) { - return compile( - fs, - stubs, - absolutePath, - fs.readFileSync(absolutePath, "utf8") - ); - } - - throw Object.assign( - new Error(`Cannot find module '${absolutePath}'`), - { code: "MODULE_NOT_FOUND" } - ); -} +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- /** * Define in-memory file system. @@ -285,257 +60,10 @@ function defineInMemoryFs({ return fs; } -/** - * Define stubbed `ConfigArrayFactory` class what uses the in-memory file system. - * @param {Object} options The options. - * @param {() => string} [options.cwd] The current working directory. - * @param {Object} [options.files] The initial files definition in the in-memory file system. - * @returns {{ fs: import("fs"), RelativeModuleResolver: import("../../lib/shared/relative-module-resolver"), ConfigArrayFactory: import("../../lib/cli-engine/config-array-factory")["ConfigArrayFactory"] }} The stubbed `ConfigArrayFactory` class. - */ -function defineConfigArrayFactoryWithInMemoryFileSystem({ - cwd = process.cwd, - files = {} -} = {}) { - const fs = defineInMemoryFs({ cwd, files }); - const RelativeModuleResolver = { resolve: fsResolve.bind(null, fs) }; - - /* - * Stubs for proxyquire. - * This contains the JavaScript files in `options.files`. - */ - const stubs = {}; - - stubs.fs = fs; - stubs["import-fresh"] = fsImportFresh.bind(null, fs, stubs); - stubs["../shared/relative-module-resolver"] = RelativeModuleResolver; - - /* - * Write all files to the in-memory file system and compile all JavaScript - * files then set to `stubs`. - */ - (function initFiles(directoryPath, definition) { - for (const [filename, content] of Object.entries(definition)) { - const filePath = path.resolve(directoryPath, filename); - - if (typeof content === "object") { - initFiles(filePath, content); - continue; - } - - /* - * Compile then stub if this file is a JavaScript file. - * For parsers and plugins that `require()` will import. - */ - if (path.extname(filePath) === ".js") { - Object.defineProperty(stubs, filePath, { - configurable: true, - enumerable: true, - get() { - let stub; - - try { - stub = compile(fs, stubs, filePath, content); - } catch (error) { - stub = { [ERRORED]: error }; - } - Object.defineProperty(stubs, filePath, { - configurable: true, - enumerable: true, - value: stub - }); - - return stub; - } - }); - } - } - }(cwd(), files)); - - // Load the stubbed one. - const { ConfigArrayFactory } = proxyquire(ConfigArrayFactoryPath, stubs); - - // Override the default cwd. - return { - fs, - stubs, - RelativeModuleResolver, - ConfigArrayFactory: cwd === process.cwd - ? ConfigArrayFactory - : class extends ConfigArrayFactory { - constructor(options) { - super({ cwd: cwd(), ...options }); - } - } - }; -} - -/** - * Define stubbed `CascadingConfigArrayFactory` class what uses the in-memory file system. - * @param {Object} options The options. - * @param {() => string} [options.cwd] The current working directory. - * @param {Object} [options.files] The initial files definition in the in-memory file system. - * @returns {{ fs: import("fs"), RelativeModuleResolver: import("../../../lib/shared/relative-module-resolver"), ConfigArrayFactory: import("../../../lib/cli-engine/config-array-factory")["ConfigArrayFactory"], CascadingConfigArrayFactory: import("../../../lib/cli-engine/cascading-config-array-factory")["CascadingConfigArrayFactory"] }} The stubbed `CascadingConfigArrayFactory` class. - */ -function defineCascadingConfigArrayFactoryWithInMemoryFileSystem({ - cwd = process.cwd, - files = {} -} = {}) { - const { fs, stubs, RelativeModuleResolver, ConfigArrayFactory } = - defineConfigArrayFactoryWithInMemoryFileSystem({ cwd, files }); - const loadRules = proxyquire(LoadRulesPath, stubs); - const { CascadingConfigArrayFactory } = - proxyquire(CascadingConfigArrayFactoryPath, { - "./config-array-factory": { ConfigArrayFactory }, - "./load-rules": loadRules - }); - - // Override the default cwd. - return { - fs, - RelativeModuleResolver, - ConfigArrayFactory, - CascadingConfigArrayFactory: cwd === process.cwd - ? CascadingConfigArrayFactory - : class extends CascadingConfigArrayFactory { - constructor(options) { - super({ cwd: cwd(), ...options }); - } - } - }; -} - -/** - * Define stubbed `FileEnumerator` class what uses the in-memory file system. - * @param {Object} options The options. - * @param {() => string} [options.cwd] The current working directory. - * @param {Object} [options.files] The initial files definition in the in-memory file system. - * @returns {{ fs: import("fs"), RelativeModuleResolver: import("../../../lib/shared/relative-module-resolver"), ConfigArrayFactory: import("../../../lib/cli-engine/config-array-factory")["ConfigArrayFactory"], CascadingConfigArrayFactory: import("../../../lib/cli-engine/cascading-config-array-factory")["CascadingConfigArrayFactory"], FileEnumerator: import("../../../lib/cli-engine/file-enumerator")["FileEnumerator"] }} The stubbed `FileEnumerator` class. - */ -function defineFileEnumeratorWithInMemoryFileSystem({ - cwd = process.cwd, - files = {} -} = {}) { - const { - fs, - RelativeModuleResolver, - ConfigArrayFactory, - CascadingConfigArrayFactory - } = - defineCascadingConfigArrayFactoryWithInMemoryFileSystem({ cwd, files }); - const { FileEnumerator } = proxyquire(FileEnumeratorPath, { - fs, - "./cascading-config-array-factory": { CascadingConfigArrayFactory } - }); - - // Override the default cwd. - return { - fs, - RelativeModuleResolver, - ConfigArrayFactory, - CascadingConfigArrayFactory, - FileEnumerator: cwd === process.cwd - ? FileEnumerator - : class extends FileEnumerator { - constructor(options) { - super({ cwd: cwd(), ...options }); - } - } - }; -} - -/** - * Define stubbed `CLIEngine` class what uses the in-memory file system. - * @param {Object} options The options. - * @param {() => string} [options.cwd] The current working directory. - * @param {Object} [options.files] The initial files definition in the in-memory file system. - * @returns {{ fs: import("fs"), RelativeModuleResolver: import("../../../lib/shared/relative-module-resolver"), ConfigArrayFactory: import("../../../lib/cli-engine/config-array-factory")["ConfigArrayFactory"], CascadingConfigArrayFactory: import("../../../lib/cli-engine/cascading-config-array-factory")["CascadingConfigArrayFactory"], FileEnumerator: import("../../../lib/cli-engine/file-enumerator")["FileEnumerator"], CLIEngine: import("../../../lib/cli-engine/cli-engine")["CLIEngine"], getCLIEngineInternalSlots: import("../../../lib/cli-engine/cli-engine")["getCLIEngineInternalSlots"] }} The stubbed `CLIEngine` class. - */ -function defineCLIEngineWithInMemoryFileSystem({ - cwd = process.cwd, - files = {} -} = {}) { - const { - fs, - RelativeModuleResolver, - ConfigArrayFactory, - CascadingConfigArrayFactory, - FileEnumerator - } = - defineFileEnumeratorWithInMemoryFileSystem({ cwd, files }); - const { CLIEngine, getCLIEngineInternalSlots } = proxyquire(CLIEnginePath, { - fs, - "./cascading-config-array-factory": { CascadingConfigArrayFactory }, - "./file-enumerator": { FileEnumerator }, - "../shared/relative-module-resolver": RelativeModuleResolver - }); - - // Override the default cwd. - return { - fs, - RelativeModuleResolver, - ConfigArrayFactory, - CascadingConfigArrayFactory, - FileEnumerator, - CLIEngine: cwd === process.cwd - ? CLIEngine - : class extends CLIEngine { - constructor(options) { - super({ cwd: cwd(), ...options }); - } - }, - getCLIEngineInternalSlots - }; -} - -/** - * Define stubbed `ESLint` class that uses the in-memory file system. - * @param {Object} options The options. - * @param {() => string} [options.cwd] The current working directory. - * @param {Object} [options.files] The initial files definition in the in-memory file system. - * @returns {{ fs: import("fs"), RelativeModuleResolver: import("../../lib/shared/relative-module-resolver"), ConfigArrayFactory: import("../../lib/cli-engine/config-array-factory")["ConfigArrayFactory"], CascadingConfigArrayFactory: import("../../lib/cli-engine/cascading-config-array-factory")["CascadingConfigArrayFactory"], FileEnumerator: import("../../lib/cli-engine/file-enumerator")["FileEnumerator"], ESLint: import("../../lib/eslint/eslint")["ESLint"], getCLIEngineInternalSlots: import("../../lib//eslint/eslint")["getESLintInternalSlots"] }} The stubbed `ESLint` class. - */ -function defineESLintWithInMemoryFileSystem({ - cwd = process.cwd, - files = {} -} = {}) { - const { - fs, - RelativeModuleResolver, - ConfigArrayFactory, - CascadingConfigArrayFactory, - FileEnumerator, - CLIEngine, - getCLIEngineInternalSlots - } = defineCLIEngineWithInMemoryFileSystem({ cwd, files }); - const { ESLint, getESLintPrivateMembers } = proxyquire(ESLintPath, { - "../cli-engine/cli-engine": { CLIEngine, getCLIEngineInternalSlots } - }); - - // Override the default cwd. - return { - fs, - RelativeModuleResolver, - ConfigArrayFactory, - CascadingConfigArrayFactory, - FileEnumerator, - CLIEngine, - getCLIEngineInternalSlots, - ESLint: cwd === process.cwd - ? ESLint - : class extends ESLint { - constructor(options) { - super({ cwd: cwd(), ...options }); - } - }, - getESLintPrivateMembers - }; -} +//----------------------------------------------------------------------------- +// Exports +//----------------------------------------------------------------------------- module.exports = { - defineInMemoryFs, - defineConfigArrayFactoryWithInMemoryFileSystem, - defineCascadingConfigArrayFactoryWithInMemoryFileSystem, - defineFileEnumeratorWithInMemoryFileSystem, - defineCLIEngineWithInMemoryFileSystem, - defineESLintWithInMemoryFileSystem + defineInMemoryFs }; diff --git a/eslint/tests/_utils/index.js b/eslint/tests/_utils/index.js index 7615613..08ef08a 100644 --- a/eslint/tests/_utils/index.js +++ b/eslint/tests/_utils/index.js @@ -9,12 +9,7 @@ //----------------------------------------------------------------------------- const { - defineInMemoryFs, - defineConfigArrayFactoryWithInMemoryFileSystem, - defineCascadingConfigArrayFactoryWithInMemoryFileSystem, - defineFileEnumeratorWithInMemoryFileSystem, - defineCLIEngineWithInMemoryFileSystem, - defineESLintWithInMemoryFileSystem + defineInMemoryFs } = require("./in-memory-fs"); const { createTeardown, addFile } = require("fs-teardown"); @@ -64,10 +59,5 @@ function createCustomTeardown({ cwd, files }) { module.exports = { unIndent, defineInMemoryFs, - defineConfigArrayFactoryWithInMemoryFileSystem, - defineCascadingConfigArrayFactoryWithInMemoryFileSystem, - defineFileEnumeratorWithInMemoryFileSystem, - defineCLIEngineWithInMemoryFileSystem, - defineESLintWithInMemoryFileSystem, createCustomTeardown }; diff --git a/eslint/tests/bin/eslint.js b/eslint/tests/bin/eslint.js index 7500d66..38f132b 100644 --- a/eslint/tests/bin/eslint.js +++ b/eslint/tests/bin/eslint.js @@ -5,13 +5,25 @@ "use strict"; +//----------------------------------------------------------------------------- +// Requirements +//----------------------------------------------------------------------------- + const childProcess = require("child_process"); const fs = require("fs"); const assert = require("chai").assert; const path = require("path"); +//------------------------------------------------------------------------------ +// Data +//------------------------------------------------------------------------------ + const EXECUTABLE_PATH = path.resolve(path.join(__dirname, "../../bin/eslint.js")); +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + /** * Returns a Promise for when a child process exits * @param {ChildProcess} exitingProcess The child process @@ -25,7 +37,7 @@ function awaitExit(exitingProcess) { * Asserts that the exit code of a given child process will equal the given value. * @param {ChildProcess} exitingProcess The child process * @param {number} expectedExitCode The expected exit code of the child process - * @returns {Promise} A Promise that fulfills if the exit code ends up matching, and rejects otherwise. + * @returns {Promise} A Promise that fulfills if the exit code ends up matching, and rejects otherwise. */ function assertExitCode(exitingProcess, expectedExitCode) { return awaitExit(exitingProcess).then(exitCode => { @@ -48,6 +60,10 @@ function getOutput(runningProcess) { return awaitExit(runningProcess).then(() => ({ stdout, stderr })); } +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + describe("bin/eslint.js", () => { const forkedProcesses = new Set(); @@ -89,6 +105,7 @@ describe("bin/eslint.js", () => { filePath: "", messages: [], errorCount: 0, + fatalErrorCount: 0, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, @@ -117,6 +134,14 @@ describe("bin/eslint.js", () => { return assertExitCode(child, 1); }); + it("has exit code 2 if a syntax error is thrown when exit-on-fatal-error is true", () => { + const child = runESLint(["--stdin", "--no-eslintrc", "--exit-on-fatal-error"]); + + child.stdin.write("This is not valid JS syntax.\n"); + child.stdin.end(); + return assertExitCode(child, 2); + }); + it("has exit code 1 if a linting error occurs", () => { const child = runESLint(["--stdin", "--no-eslintrc", "--rule", "semi:2"]); @@ -366,10 +391,11 @@ describe("bin/eslint.js", () => { const child = runESLint(["--no-ignore", invalidConfig]); const exitCodeAssertion = assertExitCode(child, 2); const outputAssertion = getOutput(child).then(output => { - const expectedSubstring = ": bad indentation of a mapping entry at line"; - assert.strictEqual(output.stdout, ""); - assert.include(output.stderr, expectedSubstring); + assert.match( + output.stderr, + /: bad indentation of a mapping entry \(\d+:\d+\)/u // a part of the error message from `js-yaml` dependency + ); }); return Promise.all([exitCodeAssertion, outputAssertion]); diff --git a/eslint/tests/fixtures/code-path-analysis/class-fields-init--arrow-function.js b/eslint/tests/fixtures/code-path-analysis/class-fields-init--arrow-function.js new file mode 100644 index 0000000..9005f13 --- /dev/null +++ b/eslint/tests/fixtures/code-path-analysis/class-fields-init--arrow-function.js @@ -0,0 +1,37 @@ +/*expected +initial->s3_1->final; +*/ +/*expected +initial->s2_1->final; +*/ +/*expected +initial->s1_1->final; +*/ + +class Foo { a = () => b } + +/*DOT +digraph { +node[shape=box,style="rounded,filled",fillcolor=white]; +initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; +final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; +s3_1[label="ArrowFunctionExpression:enter\nIdentifier (b)\nArrowFunctionExpression:exit"]; +initial->s3_1->final; +} + +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s2_1[label="ArrowFunctionExpression"]; + initial->s2_1->final; +} + +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nPropertyDefinition:enter\nIdentifier (a)\nArrowFunctionExpression\nPropertyDefinition:exit\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"]; + initial->s1_1->final; +} +*/ diff --git a/eslint/tests/fixtures/code-path-analysis/class-fields-init--call-expression.js b/eslint/tests/fixtures/code-path-analysis/class-fields-init--call-expression.js new file mode 100644 index 0000000..ec43f0a --- /dev/null +++ b/eslint/tests/fixtures/code-path-analysis/class-fields-init--call-expression.js @@ -0,0 +1,26 @@ +/*expected +initial->s2_1->final; +*/ +/*expected +initial->s1_1->final; +*/ + +class Foo { a = b() } + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s2_1[label="CallExpression:enter\nIdentifier (b)\nCallExpression:exit"]; + initial->s2_1->final; +} + +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nPropertyDefinition:enter\nIdentifier (a)\nCallExpression\nPropertyDefinition:exit\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"]; + initial->s1_1->final; +} +*/ diff --git a/eslint/tests/fixtures/code-path-analysis/class-fields-init--conditional.js b/eslint/tests/fixtures/code-path-analysis/class-fields-init--conditional.js new file mode 100644 index 0000000..e763ca9 --- /dev/null +++ b/eslint/tests/fixtures/code-path-analysis/class-fields-init--conditional.js @@ -0,0 +1,32 @@ +/*expected +initial->s2_1->s2_2->s2_4; +s2_1->s2_3->s2_4->final; +*/ +/*expected +initial->s1_1->final; +*/ + + +class Foo { a = b ? c : d } + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s2_1[label="ConditionalExpression:enter\nIdentifier (b)"]; + s2_2[label="Identifier (c)"]; + s2_4[label="ConditionalExpression:exit"]; + s2_3[label="Identifier (d)"]; + initial->s2_1->s2_2->s2_4; + s2_1->s2_3->s2_4->final; +} + +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nPropertyDefinition:enter\nIdentifier (a)\nConditionalExpression\nPropertyDefinition:exit\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"]; + initial->s1_1->final; +} +*/ diff --git a/eslint/tests/fixtures/code-path-analysis/class-fields-init--simple.js b/eslint/tests/fixtures/code-path-analysis/class-fields-init--simple.js new file mode 100644 index 0000000..fd249c5 --- /dev/null +++ b/eslint/tests/fixtures/code-path-analysis/class-fields-init--simple.js @@ -0,0 +1,28 @@ +/*expected +initial->s2_1->final; +*/ +/*expected +initial->s1_1->final; +*/ + +class Foo { + a = b; +} + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s2_1[label="Identifier (b)"]; + initial->s2_1->final; +} + +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nPropertyDefinition:enter\nIdentifier (a)\nIdentifier (b)\nPropertyDefinition:exit\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"]; + initial->s1_1->final; +} +*/ diff --git a/eslint/tests/fixtures/code-path-analysis/class-static-blocks--between-fields.js b/eslint/tests/fixtures/code-path-analysis/class-static-blocks--between-fields.js new file mode 100644 index 0000000..9bf8edf --- /dev/null +++ b/eslint/tests/fixtures/code-path-analysis/class-static-blocks--between-fields.js @@ -0,0 +1,65 @@ +/*expected +initial->s2_1->s2_2->s2_3; +s2_1->s2_3->final; +*/ +/*expected +initial->s3_1->s3_2->s3_3->s3_4; +s3_1->s3_4; +s3_2->s3_4->final; +*/ +/*expected +initial->s4_1->s4_2->s4_3->s4_4->s4_5; +s4_1->s4_5; +s4_2->s4_5; +s4_3->s4_5->final; +*/ +/*expected +initial->s1_1->final; +*/ +class Foo { bar = a || b; static { x || y || z } baz = p || q || r || s; } + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s2_1[label="LogicalExpression:enter\nIdentifier (a)"]; + s2_2[label="Identifier (b)"]; + s2_3[label="LogicalExpression:exit"]; + initial->s2_1->s2_2->s2_3; + s2_1->s2_3->final; +} +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s3_1[label="StaticBlock:enter\nExpressionStatement:enter\nLogicalExpression:enter\nLogicalExpression:enter\nIdentifier (x)"]; + s3_2[label="Identifier (y)\nLogicalExpression:exit"]; + s3_3[label="Identifier (z)"]; + s3_4[label="LogicalExpression:exit\nExpressionStatement:exit\nStaticBlock:exit"]; + initial->s3_1->s3_2->s3_3->s3_4; + s3_1->s3_4; + s3_2->s3_4->final; +} +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s4_1[label="LogicalExpression:enter\nLogicalExpression:enter\nLogicalExpression:enter\nIdentifier (p)"]; + s4_2[label="Identifier (q)\nLogicalExpression:exit"]; + s4_3[label="Identifier (r)\nLogicalExpression:exit"]; + s4_4[label="Identifier (s)"]; + s4_5[label="LogicalExpression:exit"]; + initial->s4_1->s4_2->s4_3->s4_4->s4_5; + s4_1->s4_5; + s4_2->s4_5; + s4_3->s4_5->final; +} +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nPropertyDefinition:enter\nIdentifier (bar)\nLogicalExpression\nPropertyDefinition:exit\nStaticBlock\nPropertyDefinition:enter\nIdentifier (baz)\nLogicalExpression\nPropertyDefinition:exit\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"]; + initial->s1_1->final; +} +*/ diff --git a/eslint/tests/fixtures/code-path-analysis/class-static-blocks--between-methods.js b/eslint/tests/fixtures/code-path-analysis/class-static-blocks--between-methods.js new file mode 100644 index 0000000..a6de690 --- /dev/null +++ b/eslint/tests/fixtures/code-path-analysis/class-static-blocks--between-methods.js @@ -0,0 +1,65 @@ +/*expected +initial->s2_1->s2_2->s2_3; +s2_1->s2_3->final; +*/ +/*expected +initial->s3_1->s3_2->s3_3->s3_4; +s3_1->s3_4; +s3_2->s3_4->final; +*/ +/*expected +initial->s4_1->s4_2->s4_3->s4_4->s4_5; +s4_1->s4_5; +s4_2->s4_5; +s4_3->s4_5->final; +*/ +/*expected +initial->s1_1->final; +*/ +class Foo { bar () { a || b } static { x || y || z } baz() { p || q || r || s } } + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s2_1[label="FunctionExpression:enter\nBlockStatement:enter\nExpressionStatement:enter\nLogicalExpression:enter\nIdentifier (a)"]; + s2_2[label="Identifier (b)"]; + s2_3[label="LogicalExpression:exit\nExpressionStatement:exit\nBlockStatement:exit\nFunctionExpression:exit"]; + initial->s2_1->s2_2->s2_3; + s2_1->s2_3->final; +} +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s3_1[label="StaticBlock:enter\nExpressionStatement:enter\nLogicalExpression:enter\nLogicalExpression:enter\nIdentifier (x)"]; + s3_2[label="Identifier (y)\nLogicalExpression:exit"]; + s3_3[label="Identifier (z)"]; + s3_4[label="LogicalExpression:exit\nExpressionStatement:exit\nStaticBlock:exit"]; + initial->s3_1->s3_2->s3_3->s3_4; + s3_1->s3_4; + s3_2->s3_4->final; +} +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s4_1[label="FunctionExpression:enter\nBlockStatement:enter\nExpressionStatement:enter\nLogicalExpression:enter\nLogicalExpression:enter\nLogicalExpression:enter\nIdentifier (p)"]; + s4_2[label="Identifier (q)\nLogicalExpression:exit"]; + s4_3[label="Identifier (r)\nLogicalExpression:exit"]; + s4_4[label="Identifier (s)"]; + s4_5[label="LogicalExpression:exit\nExpressionStatement:exit\nBlockStatement:exit\nFunctionExpression:exit"]; + initial->s4_1->s4_2->s4_3->s4_4->s4_5; + s4_1->s4_5; + s4_2->s4_5; + s4_3->s4_5->final; +} +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nMethodDefinition:enter\nIdentifier (bar)\nFunctionExpression\nMethodDefinition:exit\nStaticBlock\nMethodDefinition:enter\nIdentifier (baz)\nFunctionExpression\nMethodDefinition:exit\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"]; + initial->s1_1->final; +} +*/ diff --git a/eslint/tests/fixtures/code-path-analysis/class-static-blocks--conditional.js b/eslint/tests/fixtures/code-path-analysis/class-static-blocks--conditional.js new file mode 100644 index 0000000..bcdb4a7 --- /dev/null +++ b/eslint/tests/fixtures/code-path-analysis/class-static-blocks--conditional.js @@ -0,0 +1,29 @@ +/*expected +initial->s2_1->s2_2->s2_4; +s2_1->s2_3->s2_4->final; +*/ +/*expected +initial->s1_1->final; +*/ +class Foo { static { this.bar = a ? b : c; } } + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s2_1[label="StaticBlock:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nThisExpression\nIdentifier (bar)\nMemberExpression:exit\nConditionalExpression:enter\nIdentifier (a)"]; + s2_2[label="Identifier (b)"]; + s2_4[label="ConditionalExpression:exit\nAssignmentExpression:exit\nExpressionStatement:exit\nStaticBlock:exit"]; + s2_3[label="Identifier (c)"]; + initial->s2_1->s2_2->s2_4; + s2_1->s2_3->s2_4->final; +} +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nStaticBlock\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"]; + initial->s1_1->final; +} +*/ diff --git a/eslint/tests/fixtures/code-path-analysis/class-static-blocks--empty.js b/eslint/tests/fixtures/code-path-analysis/class-static-blocks--empty.js new file mode 100644 index 0000000..40a1e71 --- /dev/null +++ b/eslint/tests/fixtures/code-path-analysis/class-static-blocks--empty.js @@ -0,0 +1,24 @@ +/*expected +initial->s2_1->final; +*/ +/*expected +initial->s1_1->final; +*/ +class Foo { static {} } + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s2_1[label="StaticBlock"]; + initial->s2_1->final; +} +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nStaticBlock\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"]; + initial->s1_1->final; +} +*/ diff --git a/eslint/tests/fixtures/code-path-analysis/class-static-blocks--function-inside.js b/eslint/tests/fixtures/code-path-analysis/class-static-blocks--function-inside.js new file mode 100644 index 0000000..49df347 --- /dev/null +++ b/eslint/tests/fixtures/code-path-analysis/class-static-blocks--function-inside.js @@ -0,0 +1,34 @@ +/*expected +initial->s3_1->final; +*/ +/*expected +initial->s2_1->final; +*/ +/*expected +initial->s1_1->final; +*/ +class Foo { static { (p) => {} } } + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s3_1[label="ArrowFunctionExpression:enter\nIdentifier (p)\nBlockStatement\nArrowFunctionExpression:exit"]; + initial->s3_1->final; +} +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s2_1[label="StaticBlock:enter\nExpressionStatement:enter\nArrowFunctionExpression\nExpressionStatement:exit\nStaticBlock:exit"]; + initial->s2_1->final; +} +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nStaticBlock\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"]; + initial->s1_1->final; +} +*/ diff --git a/eslint/tests/fixtures/code-path-analysis/class-static-blocks--if-else.js b/eslint/tests/fixtures/code-path-analysis/class-static-blocks--if-else.js new file mode 100644 index 0000000..7b4a542 --- /dev/null +++ b/eslint/tests/fixtures/code-path-analysis/class-static-blocks--if-else.js @@ -0,0 +1,29 @@ +/*expected +initial->s2_1->s2_2->s2_4; +s2_1->s2_3->s2_4->final; +*/ +/*expected +initial->s1_1->final; +*/ +class Foo { static { if (bar) { this.baz = 1; } else { this.qux = 2; } } } + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s2_1[label="StaticBlock:enter\nIfStatement:enter\nIdentifier (bar)"]; + s2_2[label="BlockStatement:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nThisExpression\nIdentifier (baz)\nMemberExpression:exit\nLiteral (1)\nAssignmentExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; + s2_4[label="IfStatement:exit\nStaticBlock:exit"]; + s2_3[label="BlockStatement:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nThisExpression\nIdentifier (qux)\nMemberExpression:exit\nLiteral (2)\nAssignmentExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; + initial->s2_1->s2_2->s2_4; + s2_1->s2_3->s2_4->final; +} +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nStaticBlock\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"]; + initial->s1_1->final; +} +*/ diff --git a/eslint/tests/fixtures/code-path-analysis/class-static-blocks--multiple-1.js b/eslint/tests/fixtures/code-path-analysis/class-static-blocks--multiple-1.js new file mode 100644 index 0000000..67365fb --- /dev/null +++ b/eslint/tests/fixtures/code-path-analysis/class-static-blocks--multiple-1.js @@ -0,0 +1,34 @@ +/*expected +initial->s2_1->final; +*/ +/*expected +initial->s3_1->final; +*/ +/*expected +initial->s1_1->final; +*/ +class Foo { static { this.bar = 1; } static { this.baz = 2; } } + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s2_1[label="StaticBlock:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nThisExpression\nIdentifier (bar)\nMemberExpression:exit\nLiteral (1)\nAssignmentExpression:exit\nExpressionStatement:exit\nStaticBlock:exit"]; + initial->s2_1->final; +} +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s3_1[label="StaticBlock:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nThisExpression\nIdentifier (baz)\nMemberExpression:exit\nLiteral (2)\nAssignmentExpression:exit\nExpressionStatement:exit\nStaticBlock:exit"]; + initial->s3_1->final; +} +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nStaticBlock\nStaticBlock\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"]; + initial->s1_1->final; +} +*/ diff --git a/eslint/tests/fixtures/code-path-analysis/class-static-blocks--multiple-2.js b/eslint/tests/fixtures/code-path-analysis/class-static-blocks--multiple-2.js new file mode 100644 index 0000000..218d18a --- /dev/null +++ b/eslint/tests/fixtures/code-path-analysis/class-static-blocks--multiple-2.js @@ -0,0 +1,45 @@ +/*expected +initial->s2_1->s2_2->s2_3->s2_4; +s2_1->s2_4; +s2_2->s2_4->final; +*/ +/*expected +initial->s3_1->s3_2->s3_3; +s3_1->s3_3->final; +*/ +/*expected +initial->s1_1->final; +*/ +class Foo { static { x || y || z } static { p || q } } + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s2_1[label="StaticBlock:enter\nExpressionStatement:enter\nLogicalExpression:enter\nLogicalExpression:enter\nIdentifier (x)"]; + s2_2[label="Identifier (y)\nLogicalExpression:exit"]; + s2_3[label="Identifier (z)"]; + s2_4[label="LogicalExpression:exit\nExpressionStatement:exit\nStaticBlock:exit"]; + initial->s2_1->s2_2->s2_3->s2_4; + s2_1->s2_4; + s2_2->s2_4->final; +} +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s3_1[label="StaticBlock:enter\nExpressionStatement:enter\nLogicalExpression:enter\nIdentifier (p)"]; + s3_2[label="Identifier (q)"]; + s3_3[label="LogicalExpression:exit\nExpressionStatement:exit\nStaticBlock:exit"]; + initial->s3_1->s3_2->s3_3; + s3_1->s3_3->final; +} +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nStaticBlock\nStaticBlock\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"]; + initial->s1_1->final; +} +*/ diff --git a/eslint/tests/fixtures/code-path-analysis/class-static-blocks--simple.js b/eslint/tests/fixtures/code-path-analysis/class-static-blocks--simple.js new file mode 100644 index 0000000..06bb737 --- /dev/null +++ b/eslint/tests/fixtures/code-path-analysis/class-static-blocks--simple.js @@ -0,0 +1,24 @@ +/*expected +initial->s2_1->final; +*/ +/*expected +initial->s1_1->final; +*/ +class Foo { static { this.bar = baz; } } + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s2_1[label="StaticBlock:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nThisExpression\nIdentifier (bar)\nMemberExpression:exit\nIdentifier (baz)\nAssignmentExpression:exit\nExpressionStatement:exit\nStaticBlock:exit"]; + initial->s2_1->final; +} +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program:enter\nClassDeclaration:enter\nIdentifier (Foo)\nClassBody:enter\nStaticBlock\nClassBody:exit\nClassDeclaration:exit\nProgram:exit"]; + initial->s1_1->final; +} +*/ diff --git a/eslint/tests/fixtures/code-path-analysis/function--new.js b/eslint/tests/fixtures/code-path-analysis/function--new.js new file mode 100644 index 0000000..1aa471e --- /dev/null +++ b/eslint/tests/fixtures/code-path-analysis/function--new.js @@ -0,0 +1,30 @@ +/*expected +initial->s2_1->s2_2->s2_4; +s2_1->s2_3->s2_4->final; +*/ +/*expected +initial->s1_1->final; +*/ +function Foo() { this.a = b ? c : d }; new Foo() + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s2_1[label="FunctionDeclaration:enter\nIdentifier (Foo)\nBlockStatement:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nMemberExpression:enter\nThisExpression\nIdentifier (a)\nMemberExpression:exit\nConditionalExpression:enter\nIdentifier (b)"]; + s2_2[label="Identifier (c)"]; + s2_4[label="ConditionalExpression:exit\nAssignmentExpression:exit\nExpressionStatement:exit\nBlockStatement:exit\nFunctionDeclaration:exit"]; + s2_3[label="Identifier (d)"]; + initial->s2_1->s2_2->s2_4; + s2_1->s2_3->s2_4->final; +} + +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program:enter\nFunctionDeclaration\nEmptyStatement\nExpressionStatement:enter\nNewExpression:enter\nIdentifier (Foo)\nNewExpression:exit\nExpressionStatement:exit\nProgram:exit"]; + initial->s1_1->final; +} +*/ diff --git a/eslint/tests/fixtures/code-path-analysis/object-literal--conditional.js b/eslint/tests/fixtures/code-path-analysis/object-literal--conditional.js new file mode 100644 index 0000000..c5a4145 --- /dev/null +++ b/eslint/tests/fixtures/code-path-analysis/object-literal--conditional.js @@ -0,0 +1,20 @@ +/*expected +initial->s1_1->s1_2->s1_4; +s1_1->s1_3->s1_4->final; +*/ + +x = { a: b ? c : d } + +/*DOT +digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program:enter\nExpressionStatement:enter\nAssignmentExpression:enter\nIdentifier (x)\nObjectExpression:enter\nProperty:enter\nIdentifier (a)\nConditionalExpression:enter\nIdentifier + (b)"]; + s1_2[label="Identifier (c)"]; + s1_4[label="ConditionalExpression:exit\nProperty:exit\nObjectExpression:exit\nAssignmentExpression:exit\nExpressionStatement:exit\nProgram:exit"]; + s1_3[label="Identifier (d)"]; + initial->s1_1->s1_2->s1_4; + s1_1->s1_3->s1_4->final; +}*/ diff --git a/eslint/tests/fixtures/exit-on-fatal-error/fatal-error.js b/eslint/tests/fixtures/exit-on-fatal-error/fatal-error.js new file mode 100644 index 0000000..53a47a5 --- /dev/null +++ b/eslint/tests/fixtures/exit-on-fatal-error/fatal-error.js @@ -0,0 +1 @@ +var foo 1 \ No newline at end of file diff --git a/eslint/tests/fixtures/exit-on-fatal-error/no-fatal-error-rule-violation.js b/eslint/tests/fixtures/exit-on-fatal-error/no-fatal-error-rule-violation.js new file mode 100644 index 0000000..a23ad4a --- /dev/null +++ b/eslint/tests/fixtures/exit-on-fatal-error/no-fatal-error-rule-violation.js @@ -0,0 +1,3 @@ +/*eslint no-unused-vars: "error"*/ + +var foo = 1; \ No newline at end of file diff --git a/eslint/tests/fixtures/exit-on-fatal-error/no-fatal-error.js b/eslint/tests/fixtures/exit-on-fatal-error/no-fatal-error.js new file mode 100644 index 0000000..d5814b8 --- /dev/null +++ b/eslint/tests/fixtures/exit-on-fatal-error/no-fatal-error.js @@ -0,0 +1 @@ +var foo = 1; \ No newline at end of file diff --git a/eslint/tests/fixtures/parsers/empty-program-parser.js b/eslint/tests/fixtures/parsers/empty-program-parser.js new file mode 100644 index 0000000..7f336cd --- /dev/null +++ b/eslint/tests/fixtures/parsers/empty-program-parser.js @@ -0,0 +1,27 @@ +"use strict"; + +exports.parse = function (text, parserOptions) { + return { + "type": "Program", + "start": 0, + "end": 0, + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 0 + } + }, + "range": [ + 0, + 0 + ], + "body": [], + "sourceType": "script", + "comments": [], + "tokens": [] + }; +}; diff --git a/eslint/tests/fixtures/parsers/enhanced-parser3.js b/eslint/tests/fixtures/parsers/enhanced-parser3.js index bc08e3d..fb57e80 100644 --- a/eslint/tests/fixtures/parsers/enhanced-parser3.js +++ b/eslint/tests/fixtures/parsers/enhanced-parser3.js @@ -1,8 +1,7 @@ "use strict"; const assert = require("assert"); -const ScopeManager = require("eslint-scope/lib/scope-manager"); -const Referencer = require("eslint-scope/lib/referencer"); +const { ScopeManager, Referencer } = require("eslint-scope"); const vk = require("eslint-visitor-keys"); class EnhancedReferencer extends Referencer { diff --git a/eslint/tests/fixtures/parsers/keyword-spacing/prefix-cast-operator-no-space.js b/eslint/tests/fixtures/parsers/keyword-spacing/prefix-cast-operator-no-space.js new file mode 100644 index 0000000..9021044 --- /dev/null +++ b/eslint/tests/fixtures/parsers/keyword-spacing/prefix-cast-operator-no-space.js @@ -0,0 +1,125 @@ +"use strict"; + +/** + * Parser: @typescript-eslint/parser@5.0.0 + * + * Source code: + * this.blah + */ + +exports.parse = () => ({ + type: "Program", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "TSTypeAssertion", + typeAnnotation: { + type: "TSTypeReference", + typeName: { + type: "Identifier", + name: "Thing", + range: [1, 6], + loc: { + start: { line: 1, column: 1 }, + end: { line: 1, column: 6 }, + }, + }, + range: [1, 6], + loc: { + start: { line: 1, column: 1 }, + end: { line: 1, column: 6 }, + }, + }, + expression: { + type: "MemberExpression", + object: { + type: "ThisExpression", + range: [7, 11], + loc: { + start: { line: 1, column: 7 }, + end: { line: 1, column: 11 }, + }, + }, + property: { + type: "Identifier", + name: "blah", + range: [12, 16], + loc: { + start: { line: 1, column: 12 }, + end: { line: 1, column: 16 }, + }, + }, + computed: false, + optional: false, + range: [7, 16], + loc: { + start: { line: 1, column: 7 }, + end: { line: 1, column: 16 }, + }, + }, + range: [0, 16], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 16 }, + }, + }, + range: [0, 16], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 16 }, + }, + }, + ], + sourceType: "script", + range: [0, 16], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 16 } }, + tokens: [ + { + type: "Punctuator", + value: "<", + range: [0, 1], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } }, + }, + { + type: "Identifier", + value: "Thing", + range: [1, 6], + loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 6 } }, + }, + { + type: "Punctuator", + value: ">", + range: [6, 7], + loc: { start: { line: 1, column: 6 }, end: { line: 1, column: 7 } }, + }, + { + type: "Keyword", + value: "this", + range: [7, 11], + loc: { + start: { line: 1, column: 7 }, + end: { line: 1, column: 11 }, + }, + }, + { + type: "Punctuator", + value: ".", + range: [11, 12], + loc: { + start: { line: 1, column: 11 }, + end: { line: 1, column: 12 }, + }, + }, + { + type: "Identifier", + value: "blah", + range: [12, 16], + loc: { + start: { line: 1, column: 12 }, + end: { line: 1, column: 16 }, + }, + }, + ], + comments: [], +}); diff --git a/eslint/tests/fixtures/parsers/keyword-spacing/prefix-cast-operator-space.js b/eslint/tests/fixtures/parsers/keyword-spacing/prefix-cast-operator-space.js new file mode 100644 index 0000000..00ae0f6 --- /dev/null +++ b/eslint/tests/fixtures/parsers/keyword-spacing/prefix-cast-operator-space.js @@ -0,0 +1,125 @@ +"use strict"; + +/** + * Parser: @typescript-eslint/parser@5.0.0 + * + * Source code: + * this.blah + */ + +exports.parse = () => ({ + type: "Program", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "TSTypeAssertion", + typeAnnotation: { + type: "TSTypeReference", + typeName: { + type: "Identifier", + name: "Thing", + range: [1, 6], + loc: { + start: { line: 1, column: 1 }, + end: { line: 1, column: 6 }, + }, + }, + range: [1, 6], + loc: { + start: { line: 1, column: 1 }, + end: { line: 1, column: 6 }, + }, + }, + expression: { + type: "MemberExpression", + object: { + type: "ThisExpression", + range: [8, 12], + loc: { + start: { line: 1, column: 8 }, + end: { line: 1, column: 12 }, + }, + }, + property: { + type: "Identifier", + name: "blah", + range: [13, 17], + loc: { + start: { line: 1, column: 13 }, + end: { line: 1, column: 17 }, + }, + }, + computed: false, + optional: false, + range: [8, 17], + loc: { + start: { line: 1, column: 8 }, + end: { line: 1, column: 17 }, + }, + }, + range: [0, 17], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 17 }, + }, + }, + range: [0, 17], + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 17 }, + }, + }, + ], + sourceType: "script", + range: [0, 17], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 17 } }, + tokens: [ + { + type: "Punctuator", + value: "<", + range: [0, 1], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } }, + }, + { + type: "Identifier", + value: "Thing", + range: [1, 6], + loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 6 } }, + }, + { + type: "Punctuator", + value: ">", + range: [6, 7], + loc: { start: { line: 1, column: 6 }, end: { line: 1, column: 7 } }, + }, + { + type: "Keyword", + value: "this", + range: [8, 12], + loc: { + start: { line: 1, column: 8 }, + end: { line: 1, column: 12 }, + }, + }, + { + type: "Punctuator", + value: ".", + range: [12, 13], + loc: { + start: { line: 1, column: 12 }, + end: { line: 1, column: 13 }, + }, + }, + { + type: "Identifier", + value: "blah", + range: [13, 17], + loc: { + start: { line: 1, column: 13 }, + end: { line: 1, column: 17 }, + }, + }, + ], + comments: [], +}); diff --git a/eslint/tests/fixtures/rules/make-syntax-error-rule.js b/eslint/tests/fixtures/rules/make-syntax-error-rule.js index 528b4b0..fce60a5 100644 --- a/eslint/tests/fixtures/rules/make-syntax-error-rule.js +++ b/eslint/tests/fixtures/rules/make-syntax-error-rule.js @@ -1,14 +1,19 @@ -module.exports = function(context) { - return { - Program: function(node) { - context.report({ - node: node, - message: "ERROR", - fix: function(fixer) { - return fixer.insertTextAfter(node, "this is a syntax error."); - } - }); - } - }; +module.exports = { + meta: { + schema: [], + fixable: "code" + }, + create(context) { + return { + Program: function(node) { + context.report({ + node: node, + message: "ERROR", + fix: function(fixer) { + return fixer.insertTextAfter(node, "this is a syntax error."); + } + }); + } + }; + } }; -module.exports.schema = []; diff --git a/eslint/tests/fixtures/rules/wrong/custom-rule.js b/eslint/tests/fixtures/rules/wrong/custom-rule.js index 9a64230..9cc26c9 100644 --- a/eslint/tests/fixtures/rules/wrong/custom-rule.js +++ b/eslint/tests/fixtures/rules/wrong/custom-rule.js @@ -1,5 +1,3 @@ module.exports = function() { - - "use strict"; - return (null).something; + throw new Error("Boom!"); }; diff --git a/eslint/tests/fixtures/testers/rule-tester/fixes-one-problem.js b/eslint/tests/fixtures/testers/rule-tester/fixes-one-problem.js index 9ed9ca7..e692cae 100644 --- a/eslint/tests/fixtures/testers/rule-tester/fixes-one-problem.js +++ b/eslint/tests/fixtures/testers/rule-tester/fixes-one-problem.js @@ -5,19 +5,24 @@ "use strict"; -module.exports = context => { - return { - Program(node) { - context.report({ - node, - message: "No programs allowed." - }); +module.exports = { + meta: { + fixable: "code" + }, + create(context) { + return { + Program(node) { + context.report({ + node, + message: "No programs allowed." + }); - context.report({ - node, - message: "Seriously, no programs allowed.", - fix: fixer => fixer.remove(node) - }); + context.report({ + node, + message: "Seriously, no programs allowed.", + fix: fixer => fixer.remove(node) + }); + } } } }; diff --git a/eslint/tests/fixtures/testers/rule-tester/suggestions.js b/eslint/tests/fixtures/testers/rule-tester/suggestions.js index df97e6d..ecedde0 100644 --- a/eslint/tests/fixtures/testers/rule-tester/suggestions.js +++ b/eslint/tests/fixtures/testers/rule-tester/suggestions.js @@ -1,6 +1,7 @@ "use strict"; module.exports.basic = { + meta: { hasSuggestions: true }, create(context) { return { Identifier(node) { @@ -25,7 +26,8 @@ module.exports.withMessageIds = { avoidFoo: "Avoid using identifiers named '{{ name }}'.", unused: "An unused key", renameFoo: "Rename identifier 'foo' to '{{ newName }}'" - } + }, + hasSuggestions: true }, create(context) { return { @@ -57,4 +59,16 @@ module.exports.withMessageIds = { } }; - +module.exports.withoutHasSuggestionsProperty = { + create(context) { + return { + Identifier(node) { + context.report({ + node, + message: "some message", + suggest: [{ desc: "some suggestion", fix: fixer => fixer.replaceText(node, 'bar') }] + }); + } + }; + } +}; diff --git a/eslint/tests/lib/api.js b/eslint/tests/lib/api.js index f943f52..074d206 100644 --- a/eslint/tests/lib/api.js +++ b/eslint/tests/lib/api.js @@ -5,21 +5,33 @@ "use strict"; +//----------------------------------------------------------------------------- +// Requirements +//----------------------------------------------------------------------------- + const assert = require("chai").assert, api = require("../../lib/api"); +//----------------------------------------------------------------------------- +// Tests +//----------------------------------------------------------------------------- + describe("api", () => { it("should have RuleTester exposed", () => { assert.isFunction(api.RuleTester); }); - it("should have CLIEngine exposed", () => { - assert.isFunction(api.CLIEngine); + it("should not have CLIEngine exposed", () => { + assert.isUndefined(api.CLIEngine); + }); + + it("should not have linter exposed", () => { + assert.isUndefined(api.linter); }); - it("should have linter exposed", () => { - assert.isObject(api.linter); + it("should have Linter exposed", () => { + assert.isFunction(api.Linter); }); it("should have SourceCode exposed", () => { diff --git a/eslint/tests/lib/cli-engine/cli-engine.js b/eslint/tests/lib/cli-engine/cli-engine.js index 59243b0..31f59bf 100644 --- a/eslint/tests/lib/cli-engine/cli-engine.js +++ b/eslint/tests/lib/cli-engine/cli-engine.js @@ -16,7 +16,11 @@ const assert = require("chai").assert, fs = require("fs"), os = require("os"), hash = require("../../../lib/cli-engine/hash"), - { CascadingConfigArrayFactory } = require("@eslint/eslintrc/lib/cascading-config-array-factory"), + { + Legacy: { + CascadingConfigArrayFactory + } + } = require("@eslint/eslintrc"), { unIndent, createCustomTeardown } = require("../../_utils"); const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); @@ -40,10 +44,10 @@ describe("CLIEngine", () => { originalDir = process.cwd(), fixtureDir = path.resolve(fs.realpathSync(os.tmpdir()), "eslint/fixtures"); - /** @type {import("../../../lib/cli-engine")["CLIEngine"]} */ + /** @type {import("../../../lib/cli-engine").CLIEngine} */ let CLIEngine; - /** @type {import("../../../lib/cli-engine/cli-engine")["getCLIEngineInternalSlots"]} */ + /** @type {import("../../../lib/cli-engine/cli-engine").getCLIEngineInternalSlots} */ let getCLIEngineInternalSlots; /** @@ -69,14 +73,13 @@ describe("CLIEngine", () => { * @private */ function cliEngineWithPlugins(options) { - const engine = new CLIEngine(options); - - // load the mocked plugins - engine.addPlugin(examplePluginName, examplePlugin); - engine.addPlugin(examplePluginNameWithNamespace, examplePlugin); - engine.addPlugin(examplePreprocessorName, require("../../fixtures/processors/custom-processor")); - - return engine; + return new CLIEngine(options, { + preloadedPlugins: { + [examplePluginName]: examplePlugin, + [examplePluginNameWithNamespace]: examplePlugin, + [examplePreprocessorName]: require("../../fixtures/processors/custom-processor") + } + }); } // copy into clean area so as not to get "infected" by this project's .eslintrc files @@ -88,7 +91,7 @@ describe("CLIEngine", () => { * exceeds the default test timeout, so raise it just for this hook. * Mocha uses `this` to set timeouts on an individual hook level. */ - this.timeout(60 * 1000); // eslint-disable-line no-invalid-this + this.timeout(60 * 1000); // eslint-disable-line no-invalid-this -- Mocha API shell.mkdir("-p", fixtureDir); shell.cp("-r", "./tests/fixtures/.", fixtureDir); }); @@ -116,7 +119,7 @@ describe("CLIEngine", () => { it("should report one fatal message when given a path by --ignore-path that is not a file when ignore is true.", () => { assert.throws(() => { - // eslint-disable-next-line no-new + // eslint-disable-next-line no-new -- Testing synchronous throwing new CLIEngine({ ignorePath: fixtureDir }); }, `Cannot read .eslintignore file: ${fixtureDir}\nError: EISDIR: illegal operation on a directory, read`); }); @@ -125,7 +128,7 @@ describe("CLIEngine", () => { it("should not modify baseConfig when format is specified", () => { const customBaseConfig = { root: true }; - new CLIEngine({ baseConfig: customBaseConfig, format: "foo" }); // eslint-disable-line no-new + new CLIEngine({ baseConfig: customBaseConfig, format: "foo" }); // eslint-disable-line no-new -- Test side effects assert.deepStrictEqual(customBaseConfig, { root: true }); }); @@ -144,6 +147,7 @@ describe("CLIEngine", () => { assert.strictEqual(report.results.length, 1); assert.strictEqual(report.errorCount, 5); assert.strictEqual(report.warningCount, 0); + assert.strictEqual(report.fatalErrorCount, 0); assert.strictEqual(report.fixableErrorCount, 3); assert.strictEqual(report.fixableWarningCount, 0); assert.strictEqual(report.results[0].messages.length, 5); @@ -310,6 +314,7 @@ describe("CLIEngine", () => { messages: [], errorCount: 0, warningCount: 0, + fatalErrorCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, output: "var bar = foo;" @@ -317,6 +322,7 @@ describe("CLIEngine", () => { ], errorCount: 0, warningCount: 0, + fatalErrorCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, usedDeprecatedRules: [] @@ -519,6 +525,7 @@ describe("CLIEngine", () => { ], errorCount: 1, warningCount: 0, + fatalErrorCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, source: "var bar = foo" @@ -526,6 +533,7 @@ describe("CLIEngine", () => { ], errorCount: 1, warningCount: 0, + fatalErrorCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, usedDeprecatedRules: [] @@ -562,6 +570,7 @@ describe("CLIEngine", () => { ], errorCount: 1, warningCount: 0, + fatalErrorCount: 1, fixableErrorCount: 0, fixableWarningCount: 0, output: "var bar = foothis is a syntax error." @@ -569,6 +578,7 @@ describe("CLIEngine", () => { ], errorCount: 1, warningCount: 0, + fatalErrorCount: 1, fixableErrorCount: 0, fixableWarningCount: 0, usedDeprecatedRules: [] @@ -604,6 +614,7 @@ describe("CLIEngine", () => { ], errorCount: 1, warningCount: 0, + fatalErrorCount: 1, fixableErrorCount: 0, fixableWarningCount: 0, source: "var bar =" @@ -611,6 +622,7 @@ describe("CLIEngine", () => { ], errorCount: 1, warningCount: 0, + fatalErrorCount: 1, fixableErrorCount: 0, fixableWarningCount: 0, usedDeprecatedRules: [] @@ -692,6 +704,7 @@ describe("CLIEngine", () => { ], errorCount: 1, warningCount: 0, + fatalErrorCount: 1, fixableErrorCount: 0, fixableWarningCount: 0, source: "var bar = foothis is a syntax error.\n return bar;" @@ -699,6 +712,7 @@ describe("CLIEngine", () => { ], errorCount: 1, warningCount: 0, + fatalErrorCount: 1, fixableErrorCount: 0, fixableWarningCount: 0, usedDeprecatedRules: [] @@ -726,7 +740,7 @@ describe("CLIEngine", () => { const Module = require("module"); let originalFindPath = null; - /* eslint-disable no-underscore-dangle */ + /* eslint-disable no-underscore-dangle -- Private Node API overriding */ before(() => { originalFindPath = Module._findPath; Module._findPath = function(id, ...otherArgs) { @@ -739,7 +753,7 @@ describe("CLIEngine", () => { after(() => { Module._findPath = originalFindPath; }); - /* eslint-enable no-underscore-dangle */ + /* eslint-enable no-underscore-dangle -- Private Node API overriding */ it("should resolve 'plugins:[\"@scope\"]' to 'node_modules/@scope/eslint-plugin'.", () => { engine = new CLIEngine({ cwd: getFixturePath("plugin-shorthand/basic") }); @@ -777,7 +791,7 @@ describe("CLIEngine", () => { describe("executeOnFiles()", () => { - /** @type {InstanceType} */ + /** @type {InstanceType} */ let engine; it("should use correct parser when custom parser is specified", () => { @@ -1684,6 +1698,7 @@ describe("CLIEngine", () => { messages: [], errorCount: 0, warningCount: 0, + fatalErrorCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, output: "true ? \"yes\" : \"no\";\n" @@ -1693,6 +1708,7 @@ describe("CLIEngine", () => { messages: [], errorCount: 0, warningCount: 0, + fatalErrorCount: 0, fixableErrorCount: 0, fixableWarningCount: 0 }, @@ -1713,6 +1729,7 @@ describe("CLIEngine", () => { ], errorCount: 1, warningCount: 0, + fatalErrorCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, output: "var msg = \"hi\";\nif (msg == \"hi\") {\n\n}\n" @@ -1734,6 +1751,7 @@ describe("CLIEngine", () => { ], errorCount: 1, warningCount: 0, + fatalErrorCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, output: "var msg = \"hi\" + foo;\n" @@ -2132,10 +2150,16 @@ describe("CLIEngine", () => { useEslintrc: false, plugins: ["test"], rules: { "test/example-rule": 1 } + }, { + preloadedPlugins: { + "eslint-plugin-test": { + rules: { + "example-rule": require("../../fixtures/rules/custom-rule") + } + } + } }); - engine.addPlugin("eslint-plugin-test", { rules: { "example-rule": require("../../fixtures/rules/custom-rule") } }); - const report = engine.executeOnFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); assert.strictEqual(report.results.length, 1); @@ -2849,16 +2873,18 @@ describe("CLIEngine", () => { }, extensions: ["js", "txt"], cwd: path.join(fixtureDir, "..") - }); - - engine.addPlugin("test-processor", { - processors: { - ".txt": { - preprocess(text) { - return [text]; - }, - postprocess(messages) { - return messages[0]; + }, { + preloadedPlugins: { + "test-processor": { + processors: { + ".txt": { + preprocess(text) { + return [text]; + }, + postprocess(messages) { + return messages[0]; + } + } } } } @@ -2892,17 +2918,19 @@ describe("CLIEngine", () => { }, extensions: ["js", "txt"], cwd: path.join(fixtureDir, "..") - }); - - engine.addPlugin("test-processor", { - processors: { - ".txt": { - preprocess(text) { - return [text.replace("a()", "b()")]; - }, - postprocess(messages) { - messages[0][0].ruleId = "post-processed"; - return messages[0]; + }, { + preloadedPlugins: { + "test-processor": { + processors: { + ".txt": { + preprocess(text) { + return [text.replace("a()", "b()")]; + }, + postprocess(messages) { + messages[0][0].ruleId = "post-processed"; + return messages[0]; + } + } } } } @@ -2936,17 +2964,19 @@ describe("CLIEngine", () => { }, extensions: ["js", "txt"], ignore: false - }); - - engine.addPlugin("test-processor", { - processors: { - ".txt": { - preprocess(text) { - return [text.replace("a()", "b()")]; - }, - postprocess(messages) { - messages[0][0].ruleId = "post-processed"; - return messages[0]; + }, { + preloadedPlugins: { + "test-processor": { + processors: { + ".txt": { + preprocess(text) { + return [text.replace("a()", "b()")]; + }, + postprocess(messages) { + messages[0][0].ruleId = "post-processed"; + return messages[0]; + } + } } } } @@ -2988,11 +3018,13 @@ describe("CLIEngine", () => { extensions: ["js", "txt"], ignore: false, fix: true - }); - - engine.addPlugin("test-processor", { - processors: { - ".html": Object.assign({ supportsAutofix: true }, HTML_PROCESSOR) + }, { + preloadedPlugins: { + "test-processor": { + processors: { + ".html": Object.assign({ supportsAutofix: true }, HTML_PROCESSOR) + } + } } }); @@ -3012,10 +3044,16 @@ describe("CLIEngine", () => { extensions: ["js", "txt"], ignore: false, fix: true + }, { + preloadedPlugins: { + "test-processor": { + processors: { + ".html": HTML_PROCESSOR + } + } + } }); - engine.addPlugin("test-processor", { processors: { ".html": HTML_PROCESSOR } }); - const report = engine.executeOnText("", "foo.html"); assert.strictEqual(report.results[0].messages.length, 1); @@ -3031,11 +3069,13 @@ describe("CLIEngine", () => { }, extensions: ["js", "txt"], ignore: false - }); - - engine.addPlugin("test-processor", { - processors: { - ".html": Object.assign({ supportsAutofix: true }, HTML_PROCESSOR) + }, { + preloadedPlugins: { + "test-processor": { + processors: { + ".html": Object.assign({ supportsAutofix: true }, HTML_PROCESSOR) + } + } } }); @@ -4284,7 +4324,7 @@ describe("CLIEngine", () => { assert.throw(() => { try { - // eslint-disable-next-line no-new + // eslint-disable-next-line no-new -- Check for throwing new CLIEngine({ cwd }); } catch (error) { assert.strictEqual(error.messageTemplate, "failed-to-read-json"); @@ -4310,7 +4350,7 @@ describe("CLIEngine", () => { const cwd = getFixturePath("ignored-paths", "bad-package-json-ignore"); assert.throws(() => { - // eslint-disable-next-line no-new + // eslint-disable-next-line no-new -- Check for throwing new CLIEngine({ cwd }); }, "Package.json eslintIgnore property requires an array of paths"); }); @@ -4442,7 +4482,7 @@ describe("CLIEngine", () => { const ignorePath = getFixturePath("ignored-paths", "not-a-directory", ".foobaz"); assert.throws(() => { - // eslint-disable-next-line no-new + // eslint-disable-next-line no-new -- Check for throwing new CLIEngine({ ignorePath, cwd }); }, "Cannot read .eslintignore file"); }); @@ -4628,7 +4668,7 @@ describe("CLIEngine", () => { assert.isFunction(formatter); }); - it("should return null when a customer formatter doesn't exist", () => { + it("should return null when a custom formatter doesn't exist", () => { const engine = new CLIEngine(), formatterPath = getFixturePath("formatters", "doesntexist.js"), fullFormatterPath = path.resolve(formatterPath); @@ -4647,6 +4687,18 @@ describe("CLIEngine", () => { }, `There was a problem loading formatter: ${fullFormatterPath}\nError: Cannot find module '${fullFormatterPath}'`); }); + it("should throw when a built-in formatter no longer exists", () => { + const engine = new CLIEngine(); + + assert.throws(() => { + engine.getFormatter("table"); + }, "The table formatter is no longer part of core ESLint. Install it manually with `npm install -D eslint-formatter-table`"); + + assert.throws(() => { + engine.getFormatter("codeframe"); + }, "The codeframe formatter is no longer part of core ESLint. Install it manually with `npm install -D eslint-formatter-codeframe`"); + }); + it("should throw if the required formatter exists but has an error", () => { const engine = new CLIEngine(), formatterPath = getFixturePath("formatters", "broken.js"); @@ -4866,10 +4918,14 @@ describe("CLIEngine", () => { assert(engine.getRules().has("node/no-deprecated-api"), "node/no-deprecated-api is present"); }); - it("should expose the rules of the plugin that is added by 'addPlugin'.", () => { - const engine = new CLIEngine({ plugins: ["foo"] }); - - engine.addPlugin("foo", require("eslint-plugin-node")); + it("should expose the list of rules from a preloaded plugin", () => { + const engine = new CLIEngine({ + plugins: ["foo"] + }, { + preloadedPlugins: { + foo: require("eslint-plugin-node") + } + }); assert(engine.getRules().has("foo/no-deprecated-api"), "foo/no-deprecated-api is present"); }); @@ -5008,6 +5064,7 @@ describe("CLIEngine", () => { const config = { envs: ["browser"], ignore: true, + useEslintrc: false, allowInlineConfig: false, rules: { "eol-last": 0, @@ -5034,6 +5091,7 @@ describe("CLIEngine", () => { const config = { envs: ["browser"], ignore: true, + useEslintrc: false, // allowInlineConfig: true is the default rules: { @@ -5071,20 +5129,26 @@ describe("CLIEngine", () => { message: "Unused eslint-disable directive (no problems were reported).", line: 1, column: 1, + fix: { + range: [0, 20], + text: " " + }, severity: 2, nodeType: null } ], errorCount: 1, warningCount: 0, - fixableErrorCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 1, fixableWarningCount: 0, source: "/* eslint-disable */" } ], errorCount: 1, warningCount: 0, - fixableErrorCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 1, fixableWarningCount: 0, usedDeprecatedRules: [] } @@ -6155,7 +6219,8 @@ describe("CLIEngine", () => { } ], source: "a == b", - warningCount: 0 + warningCount: 0, + fatalErrorCount: 0 } ]); }); @@ -6177,7 +6242,8 @@ describe("CLIEngine", () => { fixableErrorCount: 0, fixableWarningCount: 0, messages: [], - warningCount: 0 + warningCount: 0, + fatalErrorCount: 0 } ]); }); @@ -6223,7 +6289,8 @@ describe("CLIEngine", () => { fixableErrorCount: 0, fixableWarningCount: 0, messages: [], - warningCount: 0 + warningCount: 0, + fatalErrorCount: 0 } ]); }); @@ -6258,7 +6325,8 @@ describe("CLIEngine", () => { } ], source: "a == b", - warningCount: 0 + warningCount: 0, + fatalErrorCount: 0 } ]); }); diff --git a/eslint/tests/lib/cli-engine/file-enumerator.js b/eslint/tests/lib/cli-engine/file-enumerator.js index 1ea7a3b..a1de4cd 100644 --- a/eslint/tests/lib/cli-engine/file-enumerator.js +++ b/eslint/tests/lib/cli-engine/file-enumerator.js @@ -4,16 +4,27 @@ */ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + const fs = require("fs"); const path = require("path"); const os = require("os"); const { assert } = require("chai"); const sh = require("shelljs"); -const { CascadingConfigArrayFactory } = - require("@eslint/eslintrc/lib/cascading-config-array-factory"); +const { + Legacy: { + CascadingConfigArrayFactory + } +} = require("@eslint/eslintrc"); const { createCustomTeardown } = require("../../_utils"); const { FileEnumerator } = require("../../../lib/cli-engine/file-enumerator"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + describe("FileEnumerator", () => { describe("'iterateFiles(patterns)' method should iterate files and configs.", () => { describe("with three directories ('lib', 'lib/nested', 'test') that contains 'one.js' and 'two.js'", () => { @@ -54,7 +65,7 @@ describe("FileEnumerator", () => { describe("if 'lib/*.js' was given,", () => { - /** @type {Array<{config:(typeof import('../../../lib/cli-engine'))["ConfigArray"], filePath:string, ignored:boolean}>} */ + /** @type {Array<{config:(typeof import('../../../lib/cli-engine')).ConfigArray, filePath:string, ignored:boolean}>} */ let list; beforeEach(() => { @@ -86,7 +97,7 @@ describe("FileEnumerator", () => { describe("if 'lib/**/*.js' was given,", () => { - /** @type {Array<{config:(typeof import('../../../lib/cli-engine'))["ConfigArray"], filePath:string, ignored:boolean}>} */ + /** @type {Array<{config:(typeof import('../../../lib/cli-engine')).ConfigArray, filePath:string, ignored:boolean}>} */ let list; beforeEach(() => { @@ -129,7 +140,7 @@ describe("FileEnumerator", () => { describe("if 'lib/*.js' and 'test/*.js' were given,", () => { - /** @type {Array<{config:(typeof import('../../../lib/cli-engine'))["ConfigArray"], filePath:string, ignored:boolean}>} */ + /** @type {Array<{config:(typeof import('../../../lib/cli-engine')).ConfigArray, filePath:string, ignored:boolean}>} */ let list; beforeEach(() => { @@ -215,7 +226,7 @@ describe("FileEnumerator", () => { * it just for this hook. Mocha uses `this` to set timeouts on * an individual hook level. */ - this.timeout(60 * 1000); // eslint-disable-line no-invalid-this + this.timeout(60 * 1000); // eslint-disable-line no-invalid-this -- Mocha API fixtureDir = `${os.tmpdir()}/eslint/tests/fixtures/`; sh.mkdir("-p", fixtureDir); sh.cp("-r", "./tests/fixtures/*", fixtureDir); diff --git a/eslint/tests/lib/cli-engine/formatters/codeframe.js b/eslint/tests/lib/cli-engine/formatters/codeframe.js deleted file mode 100644 index 398d6b8..0000000 --- a/eslint/tests/lib/cli-engine/formatters/codeframe.js +++ /dev/null @@ -1,483 +0,0 @@ -/** - * @fileoverview Tests for codeframe reporter. - * @author Vitor Balocco - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const assert = require("chai").assert; -const sinon = require("sinon"); -const proxyquire = require("proxyquire"); -const chalk = require("chalk"); -const path = require("path"); -const stripAnsi = require("strip-ansi"); - -// Chalk protects its methods so we need to inherit from it for Sinon to work. -const chalkStub = Object.create(chalk, { - yellow: { - value(str) { - return chalk.yellow(str); - }, - writable: true - }, - red: { - value(str) { - return chalk.red(str); - }, - writable: true - } -}); - -chalkStub.yellow.bold = chalk.yellow.bold; -chalkStub.red.bold = chalk.red.bold; - -const formatter = proxyquire("../../../../lib/cli-engine/formatters/codeframe", { chalk: chalkStub }); - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -describe("formatter:codeframe", () => { - afterEach(() => { - sinon.verifyAndRestore(); - }); - - describe("when passed no messages", () => { - const code = [{ - filePath: "foo.js", - messages: [], - errorCount: 0, - warningCount: 0 - }]; - - it("should return nothing", () => { - const result = formatter(code); - - assert.strictEqual(result, ""); - }); - }); - - describe("when passed a single warning message", () => { - const code = [{ - filePath: path.join(process.cwd(), "lib", "foo.js"), - source: "var foo = 1;\n var bar = 2;\n", - messages: [{ - message: "Unexpected foo.", - severity: 1, - line: 1, - column: 5, - ruleId: "foo" - }], - errorCount: 0, - warningCount: 1, - fixableErrorCount: 0, - fixableWarningCount: 0 - }]; - - it("should return a string in the correct format for warnings", () => { - const result = formatter(code); - - assert.strictEqual(stripAnsi(result), [ - `warning: Unexpected foo (foo) at ${path.join("lib", "foo.js")}:1:5:`, - "> 1 | var foo = 1;", - " | ^", - " 2 | var bar = 2;", - " 3 | ", - "\n", - "1 warning found." - ].join("\n")); - }); - - it("should return bold yellow summary when there are only warnings", () => { - sinon.spy(chalkStub.yellow, "bold"); - sinon.spy(chalkStub.red, "bold"); - - formatter(code); - - assert.strictEqual(chalkStub.yellow.bold.callCount, 1); - assert.strictEqual(chalkStub.red.bold.callCount, 0); - }); - - describe("when the warning is fixable", () => { - beforeEach(() => { - code[0].fixableWarningCount = 1; - }); - - it("should return a string in the correct format", () => { - const result = formatter(code); - - assert.strictEqual(stripAnsi(result), [ - `warning: Unexpected foo (foo) at ${path.join("lib", "foo.js")}:1:5:`, - "> 1 | var foo = 1;", - " | ^", - " 2 | var bar = 2;", - " 3 | ", - "\n", - "1 warning found.", - "1 warning potentially fixable with the `--fix` option." - ].join("\n")); - }); - }); - }); - - describe("when passed a single error message", () => { - const code = [{ - filePath: path.join(process.cwd(), "lib", "foo.js"), - source: "var foo = 1;\n var bar = 2;\n", - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 1, - column: 5, - ruleId: "foo" - }], - errorCount: 1, - warningCount: 0 - }]; - - it("should return a string in the correct format for errors", () => { - const result = formatter(code); - - assert.strictEqual(stripAnsi(result), [ - `error: Unexpected foo (foo) at ${path.join("lib", "foo.js")}:1:5:`, - "> 1 | var foo = 1;", - " | ^", - " 2 | var bar = 2;", - " 3 | ", - "\n", - "1 error found." - ].join("\n")); - }); - - it("should return bold red summary when there are errors", () => { - sinon.spy(chalkStub.yellow, "bold"); - sinon.spy(chalkStub.red, "bold"); - - formatter(code); - - assert.strictEqual(chalkStub.yellow.bold.callCount, 0); - assert.strictEqual(chalkStub.red.bold.callCount, 1); - }); - }); - - describe("when passed a message that ends with ' .'", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - ruleId: "foo", - message: "Unexpected .", - severity: 2, - source: "foo" - }], - errorCount: 1, - warningCount: 0 - }]; - - it("should return a string in the correct format (retaining the ' .')", () => { - const result = formatter(code); - - assert.strictEqual(stripAnsi(result), "error: Unexpected . (foo) at foo.js\n\n\n1 error found."); - }); - }); - - describe("when passed multiple messages", () => { - const code = [{ - filePath: "foo.js", - source: "const foo = 1\n", - messages: [{ - message: "Missing semicolon.", - severity: 2, - line: 1, - column: 14, - ruleId: "semi" - }, { - message: "'foo' is assigned a value but never used.", - severity: 2, - line: 1, - column: 7, - ruleId: "no-unused-vars" - }], - errorCount: 2, - warningCount: 0 - }]; - - it("should return a string with multiple entries", () => { - const result = formatter(code); - - assert.strictEqual(stripAnsi(result), [ - "error: Missing semicolon (semi) at foo.js:1:14:", - "> 1 | const foo = 1", - " | ^", - " 2 | ", - "\n", - "error: 'foo' is assigned a value but never used (no-unused-vars) at foo.js:1:7:", - "> 1 | const foo = 1", - " | ^", - " 2 | ", - "\n", - "2 errors found." - ].join("\n")); - }); - - it("should return bold red summary when at least 1 of the messages is an error", () => { - sinon.spy(chalkStub.yellow, "bold"); - sinon.spy(chalkStub.red, "bold"); - code[0].messages[0].severity = 1; - code[0].warningCount = 1; - code[0].errorCount = 1; - - formatter(code); - - assert.strictEqual(chalkStub.yellow.bold.callCount, 0); - assert.strictEqual(chalkStub.red.bold.callCount, 1); - }); - }); - - describe("when passed one file with 1 message and fixes applied", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - ruleId: "no-unused-vars", - severity: 2, - message: "'foo' is assigned a value but never used.", - line: 4, - column: 11, - source: " const foo = 1;" - }], - errorCount: 1, - warningCount: 0, - output: "function foo() {\n\n // a comment\n const foo = 1;\n}\n\n" - }]; - - it("should return a string with code preview pointing to the correct location after fixes", () => { - const result = formatter(code); - - assert.strictEqual(stripAnsi(result), [ - "error: 'foo' is assigned a value but never used (no-unused-vars) at foo.js:4:11:", - " 2 | ", - " 3 | // a comment", - "> 4 | const foo = 1;", - " | ^", - " 5 | }", - " 6 | ", - " 7 | ", - "\n", - "1 error found." - ].join("\n")); - }); - }); - - describe("when passed multiple files with 1 message each", () => { - const code = [{ - filePath: "foo.js", - source: "const foo = 1\n", - messages: [{ - message: "Missing semicolon.", - severity: 2, - line: 1, - column: 14, - ruleId: "semi" - }], - errorCount: 1, - warningCount: 0 - }, { - filePath: "bar.js", - source: "const bar = 2\n", - messages: [{ - message: "Missing semicolon.", - severity: 2, - line: 1, - column: 14, - ruleId: "semi" - }], - errorCount: 1, - warningCount: 0 - }]; - - it("should return a string with multiple entries", () => { - const result = formatter(code); - - assert.strictEqual(stripAnsi(result), [ - "error: Missing semicolon (semi) at foo.js:1:14:", - "> 1 | const foo = 1", - " | ^", - " 2 | ", - "\n", - "error: Missing semicolon (semi) at bar.js:1:14:", - "> 1 | const bar = 2", - " | ^", - " 2 | ", - "\n", - "2 errors found." - ].join("\n")); - }); - }); - - describe("when passed a fatal error message", () => { - const code = [{ - filePath: "foo.js", - source: "e{}\n", - messages: [{ - ruleId: null, - fatal: true, - severity: 2, - source: "e{}", - message: "Parsing error: Unexpected token {", - line: 1, - column: 2 - }], - errorCount: 1, - warningCount: 0 - }]; - - it("should return a string in the correct format", () => { - const result = formatter(code); - - assert.strictEqual(stripAnsi(result), [ - "error: Parsing error: Unexpected token { at foo.js:1:2:", - "> 1 | e{}", - " | ^", - " 2 | ", - "\n", - "1 error found." - ].join("\n")); - }); - }); - - describe("when passed one file not found message", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - fatal: true, - message: "Couldn't find foo.js." - }], - errorCount: 1, - warningCount: 0 - }]; - - it("should return a string without code preview (codeframe)", () => { - const result = formatter(code); - - assert.strictEqual(stripAnsi(result), "error: Couldn't find foo.js at foo.js\n\n\n1 error found."); - }); - }); - - describe("when passed a single message with no line or column", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - ruleId: "foo", - message: "Unexpected foo.", - severity: 2, - source: "foo" - }], - errorCount: 1, - warningCount: 0 - }]; - - it("should return a string without code preview (codeframe)", () => { - const result = formatter(code); - - assert.strictEqual(stripAnsi(result), "error: Unexpected foo (foo) at foo.js\n\n\n1 error found."); - }); - - it("should output filepath but without 'line:column' appended", () => { - const result = formatter(code); - - assert.strictEqual(stripAnsi(result), "error: Unexpected foo (foo) at foo.js\n\n\n1 error found."); - }); - }); - - - describe("fixable problems", () => { - it("should not output fixable problems message when no errors or warnings are fixable", () => { - const code = [{ - filePath: "foo.js", - errorCount: 1, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }] - }]; - - const result = formatter(code); - - assert.notInclude(result, "potentially fixable"); - }); - - it("should output the fixable problems message when errors are fixable", () => { - const code = [{ - filePath: "foo.js", - errorCount: 1, - warningCount: 0, - fixableErrorCount: 1, - fixableWarningCount: 0, - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }] - }]; - - const result = formatter(code); - - assert.include(result, "1 error potentially fixable with the `--fix` option."); - }); - - it("should output fixable problems message when warnings are fixable", () => { - const code = [{ - filePath: "foo.js", - errorCount: 0, - warningCount: 3, - fixableErrorCount: 0, - fixableWarningCount: 2, - messages: [{ - message: "Unexpected foo." - }] - }]; - - const result = formatter(code); - - assert.include(result, "2 warnings potentially fixable with the `--fix` option."); - }); - - it("should output the total number of fixable errors and warnings", () => { - const code = [{ - filePath: "foo.js", - errorCount: 5, - warningCount: 3, - fixableErrorCount: 5, - fixableWarningCount: 2, - messages: [{ - message: "Unexpected foo." - }] - }, { - filePath: "bar.js", - errorCount: 4, - warningCount: 2, - fixableErrorCount: 4, - fixableWarningCount: 1, - messages: [{ - message: "Unexpected bar." - }] - }]; - - const result = formatter(code); - - assert.include(result, "9 errors and 3 warnings potentially fixable with the `--fix` option."); - }); - }); - -}); diff --git a/eslint/tests/lib/cli-engine/formatters/junit.js b/eslint/tests/lib/cli-engine/formatters/junit.js index b77c177..26a2445 100644 --- a/eslint/tests/lib/cli-engine/formatters/junit.js +++ b/eslint/tests/lib/cli-engine/formatters/junit.js @@ -3,8 +3,6 @@ * @author Jamund Ferguson */ -/* jshint node:true */ - "use strict"; //------------------------------------------------------------------------------ diff --git a/eslint/tests/lib/cli-engine/formatters/stylish.js b/eslint/tests/lib/cli-engine/formatters/stylish.js index 66f6abe..24f4be9 100644 --- a/eslint/tests/lib/cli-engine/formatters/stylish.js +++ b/eslint/tests/lib/cli-engine/formatters/stylish.js @@ -14,6 +14,10 @@ const assert = require("chai").assert, proxyquire = require("proxyquire"), sinon = require("sinon"); +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + /* * Chalk protects its methods so we need to inherit from it * for Sinon to work. diff --git a/eslint/tests/lib/cli-engine/formatters/table.js b/eslint/tests/lib/cli-engine/formatters/table.js deleted file mode 100644 index 3e29caf..0000000 --- a/eslint/tests/lib/cli-engine/formatters/table.js +++ /dev/null @@ -1,324 +0,0 @@ -/** - * @fileoverview Tests for "table" reporter. - * @author Gajus Kuizinas - */ -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const assert = require("chai").assert; -const chalk = require("chalk"); -const formatter = require("../../../../lib/cli-engine/formatters/table"); - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -describe("formatter:table", () => { - const originalColorLevel = chalk.level; - - before(() => { - chalk.level = 0; - }); - - after(() => { - chalk.level = originalColorLevel; - }); - - describe("when passed no messages", () => { - const code = [ - { - filePath: "foo.js", - messages: [], - errorCount: 0, - warningCount: 0 - } - ]; - - it("should return a table of error and warning count with no messages", () => { - const expectedOutput = [ - "", - "╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗", - "║ 0 Errors ║", - "╟────────────────────────────────────────────────────────────────────────────────────────────────────────────────╢", - "║ 0 Warnings ║", - "╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝", - "" - ].join("\n"); - - const result = formatter(code); - - assert.strictEqual(result, expectedOutput); - }); - }); - - describe("when passed a single message", () => { - it("should return a string in the correct format for errors", () => { - const code = [ - { - filePath: "foo.js", - messages: [ - { - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - } - ], - errorCount: 1, - warningCount: 0 - } - ]; - - const expectedOutput = [ - "", - "foo.js", - "", - "║ Line │ Column │ Type │ Message │ Rule ID ║", - "╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢", - "║ 5 │ 10 │ error │ Unexpected foo. │ foo ║", - "", - "╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗", - "║ 1 Error ║", - "╟────────────────────────────────────────────────────────────────────────────────────────────────────────────────╢", - "║ 0 Warnings ║", - "╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝", - "" - ].join("\n"); - - const result = formatter(code); - - assert.strictEqual(result, expectedOutput); - }); - - it("should return a string in the correct format for warnings", () => { - const code = [ - { - filePath: "foo.js", - messages: [ - { - message: "Unexpected foo.", - severity: 1, - line: 5, - column: 10, - ruleId: "foo" - } - ], - errorCount: 0, - warningCount: 1 - } - ]; - - const expectedOutput = [ - "", - "foo.js", - "", - "║ Line │ Column │ Type │ Message │ Rule ID ║", - "╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢", - "║ 5 │ 10 │ warning │ Unexpected foo. │ foo ║", - "", - "╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗", - "║ 0 Errors ║", - "╟────────────────────────────────────────────────────────────────────────────────────────────────────────────────╢", - "║ 1 Warning ║", - "╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝", - "" - ].join("\n"); - - const result = formatter(code); - - assert.strictEqual(result, expectedOutput); - }); - }); - - describe("when passed a fatal error message", () => { - it("should return a string in the correct format", () => { - const code = [ - { - filePath: "foo.js", - messages: [ - { - fatal: true, - message: "Unexpected foo.", - line: 5, - column: 10, - ruleId: "foo" - } - ], - errorCount: 1, - warningCount: 0 - } - ]; - - const expectedOutput = [ - "", - "foo.js", - "", - "║ Line │ Column │ Type │ Message │ Rule ID ║", - "╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢", - "║ 5 │ 10 │ error │ Unexpected foo. │ foo ║", - "", - "╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗", - "║ 1 Error ║", - "╟────────────────────────────────────────────────────────────────────────────────────────────────────────────────╢", - "║ 0 Warnings ║", - "╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝", - "" - ].join("\n"); - - const result = formatter(code); - - assert.strictEqual(result, expectedOutput); - }); - }); - - describe("when passed multiple messages", () => { - it("should return a string with multiple entries", () => { - const code = [ - { - filePath: "foo.js", - messages: [ - { - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }, - { - message: "Unexpected bar.", - severity: 1, - line: 6, - column: 11, - ruleId: "bar" - } - ], - errorCount: 1, - warningCount: 1 - } - ]; - - const expectedOutput = [ - "", - "foo.js", - "", - "║ Line │ Column │ Type │ Message │ Rule ID ║", - "╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢", - "║ 5 │ 10 │ error │ Unexpected foo. │ foo ║", - "║ 6 │ 11 │ warning │ Unexpected bar. │ bar ║", - "", - "╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗", - "║ 1 Error ║", - "╟────────────────────────────────────────────────────────────────────────────────────────────────────────────────╢", - "║ 1 Warning ║", - "╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝", - "" - ].join("\n"); - - const result = formatter(code); - - assert.strictEqual(result, expectedOutput); - }); - }); - - describe("when passed multiple files with 1 message each", () => { - it("should return a string with multiple entries", () => { - const code = [ - { - filePath: "foo.js", - messages: [ - { - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - } - ], - errorCount: 1, - warningCount: 0 - }, { - filePath: "bar.js", - messages: [ - { - message: "Unexpected bar.", - severity: 1, - line: 6, - column: 11, - ruleId: "bar" - } - ], - errorCount: 0, - warningCount: 1 - } - ]; - - const expectedOutput = [ - "", - "foo.js", - "", - "║ Line │ Column │ Type │ Message │ Rule ID ║", - "╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢", - "║ 5 │ 10 │ error │ Unexpected foo. │ foo ║", - "", - "bar.js", - "", - "║ Line │ Column │ Type │ Message │ Rule ID ║", - "╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢", - "║ 6 │ 11 │ warning │ Unexpected bar. │ bar ║", - "", - "╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗", - "║ 1 Error ║", - "╟────────────────────────────────────────────────────────────────────────────────────────────────────────────────╢", - "║ 1 Warning ║", - "╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝", - "" - ].join("\n"); - - const result = formatter(code); - - assert.strictEqual(result, expectedOutput); - }); - }); - - describe("when passed one file not found message", () => { - it("should return a string without line and column (0, 0)", () => { - const code = [ - { - filePath: "foo.js", - messages: [ - { - fatal: true, - message: "Couldn't find foo.js." - } - ], - errorCount: 1, - warningCount: 0 - } - ]; - - const expectedOutput = [ - "", - "foo.js", - "", - "║ Line │ Column │ Type │ Message │ Rule ID ║", - "╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢", - "║ 0 │ 0 │ error │ Couldn't find foo.js. │ ║", - "", - "╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗", - "║ 1 Error ║", - "╟────────────────────────────────────────────────────────────────────────────────────────────────────────────────╢", - "║ 0 Warnings ║", - "╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝", - "" - ].join("\n"); - - const result = formatter(code); - - assert.strictEqual(result, expectedOutput); - }); - }); -}); diff --git a/eslint/tests/lib/cli-engine/load-rules.js b/eslint/tests/lib/cli-engine/load-rules.js index 1ff1470..beebecf 100644 --- a/eslint/tests/lib/cli-engine/load-rules.js +++ b/eslint/tests/lib/cli-engine/load-rules.js @@ -5,9 +5,17 @@ "use strict"; +//----------------------------------------------------------------------------- +// Requirements +//----------------------------------------------------------------------------- + const assert = require("chai").assert; const loadRules = require("../../../lib/cli-engine/load-rules"); +//----------------------------------------------------------------------------- +// Tests +//----------------------------------------------------------------------------- + describe("when given an invalid rules directory", () => { it("should throw an error", () => { assert.throws(() => { diff --git a/eslint/tests/lib/cli.js b/eslint/tests/lib/cli.js index d1fea18..1b3828b 100644 --- a/eslint/tests/lib/cli.js +++ b/eslint/tests/lib/cli.js @@ -90,7 +90,7 @@ describe("cli", () => { * exceeds the default test timeout, so raise it just for this hook. * Mocha uses `this` to set timeouts on an individual hook level. */ - this.timeout(60 * 1000); // eslint-disable-line no-invalid-this + this.timeout(60 * 1000); // eslint-disable-line no-invalid-this -- Mocha API fixtureDir = `${os.tmpdir()}/eslint/fixtures`; sh.mkdir("-p", fixtureDir); sh.cp("-r", "./tests/fixtures/.", fixtureDir); @@ -512,7 +512,7 @@ describe("cli", () => { const exit = await cli.execute(code); assert.strictEqual(exit, 2); - }, /Error while loading rule 'custom-rule': Cannot read property/u); + }, /Error while loading rule 'custom-rule': Boom!/u); }); it("should return a warning when rule is matched", async () => { @@ -803,6 +803,38 @@ describe("cli", () => { }); }); + describe("when given the exit-on-fatal-error flag", () => { + it("should not change exit code if no fatal errors are reported", async () => { + const filePath = getFixturePath("exit-on-fatal-error", "no-fatal-error.js"); + const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`); + + assert.strictEqual(exitCode, 0); + }); + + it("should exit with exit code 1 if no fatal errors are found, but rule violations are found", async () => { + const filePath = getFixturePath("exit-on-fatal-error", "no-fatal-error-rule-violation.js"); + const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`); + + assert.strictEqual(exitCode, 1); + }); + + it("should exit with exit code 2 if fatal error is found", async () => { + const filePath = getFixturePath("exit-on-fatal-error", "fatal-error.js"); + const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`); + + assert.strictEqual(exitCode, 2); + }); + + it("should exit with exit code 2 if fatal error is found in any file", async () => { + const filePath = getFixturePath("exit-on-fatal-error"); + const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`); + + assert.strictEqual(exitCode, 2); + }); + + + }); + describe("when passed --no-inline-config", () => { let localCLI; diff --git a/eslint/tests/lib/config/flat-config-array.js b/eslint/tests/lib/config/flat-config-array.js new file mode 100644 index 0000000..f6b0990 --- /dev/null +++ b/eslint/tests/lib/config/flat-config-array.js @@ -0,0 +1,1493 @@ +/** + * @fileoverview Tests for FlatConfigArray + * @author Nicholas C. Zakas + */ + +"use strict"; + +//----------------------------------------------------------------------------- +// Requirements +//----------------------------------------------------------------------------- + +const { FlatConfigArray } = require("../../../lib/config/flat-config-array"); +const assert = require("chai").assert; +const allConfig = require("../../../conf/eslint-all"); +const recommendedConfig = require("../../../conf/eslint-recommended"); + +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + +const baseConfig = { + plugins: { + "@": { + rules: { + foo: { + schema: { + type: "array", + items: [ + { + enum: ["always", "never"] + } + ], + minItems: 0, + maxItems: 1 + } + + }, + bar: { + + }, + baz: { + + }, + + // old-style + boom() {}, + + foo2: { + schema: { + type: "array", + items: { + type: "string" + }, + uniqueItems: true, + minItems: 1 + } + } + } + }, + test1: { + rules: { + match: {} + } + }, + test2: { + rules: { + nomatch: {} + } + } + } +}; + +/** + * Creates a config array with the correct default options. + * @param {*[]} configs An array of configs to use in the config array. + * @returns {FlatConfigArray} The config array; + */ +function createFlatConfigArray(configs) { + return new FlatConfigArray(configs, { + basePath: __dirname, + baseConfig + }); +} + +/** + * Asserts that a given set of configs will be merged into the given + * result config. + * @param {*[]} values An array of configs to use in the config array. + * @param {Object} result The expected merged result of the configs. + * @returns {void} + * @throws {AssertionError} If the actual result doesn't match the + * expected result. + */ +async function assertMergedResult(values, result) { + const configs = createFlatConfigArray(values); + + await configs.normalize(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config, result); +} + +/** + * Asserts that a given set of configs results in an invalid config. + * @param {*[]} values An array of configs to use in the config array. + * @param {string|RegExp} message The expected error message. + * @returns {void} + * @throws {AssertionError} If the config is valid or if the error + * has an unexpected message. + */ +async function assertInvalidConfig(values, message) { + const configs = createFlatConfigArray(values); + + await configs.normalize(); + + assert.throws(() => { + configs.getConfig("foo.js"); + }, message); +} + +/** + * Normalizes the rule configs to an array with severity to match + * how Flat Config merges rule options. + * @param {Object} rulesConfig The rules config portion of a config. + * @returns {Array} The rules config object. + */ +function normalizeRuleConfig(rulesConfig) { + const rulesConfigCopy = { + ...rulesConfig + }; + + for (const ruleId of Object.keys(rulesConfigCopy)) { + rulesConfigCopy[ruleId] = [2]; + } + + return rulesConfigCopy; +} + +//----------------------------------------------------------------------------- +// Tests +//----------------------------------------------------------------------------- + +describe("FlatConfigArray", () => { + + describe("Special configs", () => { + it("eslint:recommended is replaced with an actual config", async () => { + const configs = new FlatConfigArray(["eslint:recommended"], { basePath: __dirname }); + + await configs.normalize(); + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.rules, normalizeRuleConfig(recommendedConfig.rules)); + }); + + it("eslint:all is replaced with an actual config", async () => { + const configs = new FlatConfigArray(["eslint:all"], { basePath: __dirname }); + + await configs.normalize(); + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.rules, normalizeRuleConfig(allConfig.rules)); + }); + }); + + describe("Config Properties", () => { + + describe("settings", () => { + + it("should merge two objects", () => assertMergedResult([ + { + settings: { + a: true, + b: false + } + }, + { + settings: { + c: true, + d: false + } + } + ], { + plugins: baseConfig.plugins, + + settings: { + a: true, + b: false, + c: true, + d: false + } + })); + + it("should merge two objects when second object has overrides", () => assertMergedResult([ + { + settings: { + a: true, + b: false, + d: [1, 2], + e: [5, 6] + } + }, + { + settings: { + c: true, + a: false, + d: [3, 4] + } + } + ], { + plugins: baseConfig.plugins, + + settings: { + a: false, + b: false, + c: true, + d: [3, 4], + e: [5, 6] + } + })); + + it("should deeply merge two objects when second object has overrides", () => assertMergedResult([ + { + settings: { + object: { + a: true, + b: false + } + } + }, + { + settings: { + object: { + c: true, + a: false + } + } + } + ], { + plugins: baseConfig.plugins, + + settings: { + object: { + a: false, + b: false, + c: true + } + } + })); + + it("should merge an object and undefined into one object", () => assertMergedResult([ + { + settings: { + a: true, + b: false + } + }, + { + } + ], { + plugins: baseConfig.plugins, + + settings: { + a: true, + b: false + } + })); + + it("should merge undefined and an object into one object", () => assertMergedResult([ + { + }, + { + settings: { + a: true, + b: false + } + } + ], { + plugins: baseConfig.plugins, + + settings: { + a: true, + b: false + } + })); + + }); + + describe("plugins", () => { + + const pluginA = {}; + const pluginB = {}; + const pluginC = {}; + + it("should merge two objects", () => assertMergedResult([ + { + plugins: { + a: pluginA, + b: pluginB + } + }, + { + plugins: { + c: pluginC + } + } + ], { + plugins: { + a: pluginA, + b: pluginB, + c: pluginC, + ...baseConfig.plugins + } + })); + + it("should merge an object and undefined into one object", () => assertMergedResult([ + { + plugins: { + a: pluginA, + b: pluginB + } + }, + { + } + ], { + plugins: { + a: pluginA, + b: pluginB, + ...baseConfig.plugins + } + })); + + it("should error when attempting to redefine a plugin", async () => { + + await assertInvalidConfig([ + { + plugins: { + a: pluginA, + b: pluginB + } + }, + { + plugins: { + a: pluginC + } + } + ], "Cannot redefine plugin \"a\"."); + }); + + it("should error when plugin is not an object", async () => { + + await assertInvalidConfig([ + { + plugins: { + a: true + } + } + ], "Key \"a\": Expected an object."); + }); + + + }); + + describe("processor", () => { + + it("should merge two values when second is a string", () => { + + const stubProcessor = { + preprocess() {}, + postprocess() {} + }; + + return assertMergedResult([ + { + processor: { + preprocess() {}, + postprocess() {} + } + }, + { + plugins: { + markdown: { + processors: { + markdown: stubProcessor + } + } + }, + processor: "markdown/markdown" + } + ], { + plugins: { + markdown: { + processors: { + markdown: stubProcessor + } + }, + ...baseConfig.plugins + }, + processor: stubProcessor + }); + }); + + it("should merge two values when second is an object", () => { + + const processor = { + preprocess() { }, + postprocess() { } + }; + + return assertMergedResult([ + { + processor: "markdown/markdown" + }, + { + processor + } + ], { + plugins: baseConfig.plugins, + + processor + }); + }); + + it("should error when an invalid string is used", async () => { + + await assertInvalidConfig([ + { + processor: "foo" + } + ], "pluginName/objectName"); + }); + + it("should error when an empty string is used", async () => { + + await assertInvalidConfig([ + { + processor: "" + } + ], "pluginName/objectName"); + }); + + it("should error when an invalid processor is used", async () => { + await assertInvalidConfig([ + { + processor: {} + } + ], "Object must have a preprocess() and a postprocess() method."); + + }); + + it("should error when a processor cannot be found in a plugin", async () => { + await assertInvalidConfig([ + { + plugins: { + foo: {} + }, + processor: "foo/bar" + } + ], /Could not find "bar" in plugin "foo"/u); + + }); + + }); + + describe("linterOptions", () => { + + it("should error when an unexpected key is found", async () => { + + await assertInvalidConfig([ + { + linterOptions: { + foo: true + } + } + ], "Unexpected key \"foo\" found."); + + }); + + describe("noInlineConfig", () => { + + it("should error when an unexpected value is found", async () => { + + await assertInvalidConfig([ + { + linterOptions: { + noInlineConfig: "true" + } + } + ], "Expected a Boolean."); + }); + + it("should merge two objects when second object has overrides", () => assertMergedResult([ + { + linterOptions: { + noInlineConfig: true + } + }, + { + linterOptions: { + noInlineConfig: false + } + } + ], { + plugins: baseConfig.plugins, + + linterOptions: { + noInlineConfig: false + } + })); + + it("should merge an object and undefined into one object", () => assertMergedResult([ + { + linterOptions: { + noInlineConfig: false + } + }, + { + } + ], { + plugins: baseConfig.plugins, + + linterOptions: { + noInlineConfig: false + } + })); + + it("should merge undefined and an object into one object", () => assertMergedResult([ + { + }, + { + linterOptions: { + noInlineConfig: false + } + } + ], { + plugins: baseConfig.plugins, + + linterOptions: { + noInlineConfig: false + } + })); + + + }); + describe("reportUnusedDisableDirectives", () => { + + it("should error when an unexpected value is found", async () => { + + await assertInvalidConfig([ + { + linterOptions: { + reportUnusedDisableDirectives: "true" + } + } + ], /Expected a Boolean/u); + }); + + it("should merge two objects when second object has overrides", () => assertMergedResult([ + { + linterOptions: { + reportUnusedDisableDirectives: false + } + }, + { + linterOptions: { + reportUnusedDisableDirectives: true + } + } + ], { + plugins: baseConfig.plugins, + + linterOptions: { + reportUnusedDisableDirectives: true + } + })); + + it("should merge an object and undefined into one object", () => assertMergedResult([ + {}, + { + linterOptions: { + reportUnusedDisableDirectives: true + } + } + ], { + plugins: baseConfig.plugins, + + linterOptions: { + reportUnusedDisableDirectives: true + } + })); + + + }); + + }); + + describe("languageOptions", () => { + + it("should error when an unexpected key is found", async () => { + + await assertInvalidConfig([ + { + languageOptions: { + foo: true + } + } + ], "Unexpected key \"foo\" found."); + + }); + + it("should merge two languageOptions objects with different properties", () => assertMergedResult([ + { + languageOptions: { + ecmaVersion: 2019 + } + }, + { + languageOptions: { + sourceType: "commonjs" + } + } + ], { + plugins: baseConfig.plugins, + + languageOptions: { + ecmaVersion: 2019, + sourceType: "commonjs" + } + })); + + describe("ecmaVersion", () => { + + it("should error when an unexpected value is found", async () => { + + await assertInvalidConfig([ + { + languageOptions: { + ecmaVersion: "true" + } + } + ], "Expected a number."); + }); + + it("should merge two objects when second object has overrides", () => assertMergedResult([ + { + languageOptions: { + ecmaVersion: 2019 + } + }, + { + languageOptions: { + ecmaVersion: 2021 + } + } + ], { + plugins: baseConfig.plugins, + + languageOptions: { + ecmaVersion: 2021 + } + })); + + it("should merge an object and undefined into one object", () => assertMergedResult([ + { + languageOptions: { + ecmaVersion: 2021 + } + }, + { + } + ], { + plugins: baseConfig.plugins, + + languageOptions: { + ecmaVersion: 2021 + } + })); + + + it("should merge undefined and an object into one object", () => assertMergedResult([ + { + }, + { + languageOptions: { + ecmaVersion: 2021 + } + } + ], { + plugins: baseConfig.plugins, + + languageOptions: { + ecmaVersion: 2021 + } + })); + + + }); + + describe("sourceType", () => { + + it("should error when an unexpected value is found", async () => { + + await assertInvalidConfig([ + { + languageOptions: { + sourceType: "true" + } + } + ], "Expected \"script\", \"module\", or \"commonjs\"."); + }); + + it("should merge two objects when second object has overrides", () => assertMergedResult([ + { + languageOptions: { + sourceType: "module" + } + }, + { + languageOptions: { + sourceType: "script" + } + } + ], { + plugins: baseConfig.plugins, + + languageOptions: { + sourceType: "script" + } + })); + + it("should merge an object and undefined into one object", () => assertMergedResult([ + { + languageOptions: { + sourceType: "script" + } + }, + { + } + ], { + plugins: baseConfig.plugins, + + languageOptions: { + sourceType: "script" + } + })); + + + it("should merge undefined and an object into one object", () => assertMergedResult([ + { + }, + { + languageOptions: { + sourceType: "module" + } + } + ], { + plugins: baseConfig.plugins, + + languageOptions: { + sourceType: "module" + } + })); + + + }); + + describe("globals", () => { + + it("should error when an unexpected value is found", async () => { + + await assertInvalidConfig([ + { + languageOptions: { + globals: "true" + } + } + ], "Expected an object."); + }); + + it("should error when an unexpected key value is found", async () => { + + await assertInvalidConfig([ + { + languageOptions: { + globals: { + foo: "truex" + } + } + } + ], "Key \"foo\": Expected \"readonly\", \"writable\", or \"off\"."); + }); + + it("should error when a global has leading whitespace", async () => { + + await assertInvalidConfig([ + { + languageOptions: { + globals: { + " foo": "readonly" + } + } + } + ], /Global " foo" has leading or trailing whitespace/u); + }); + + it("should error when a global has trailing whitespace", async () => { + + await assertInvalidConfig([ + { + languageOptions: { + globals: { + "foo ": "readonly" + } + } + } + ], /Global "foo " has leading or trailing whitespace/u); + }); + + it("should merge two objects when second object has different keys", () => assertMergedResult([ + { + languageOptions: { + globals: { + foo: "readonly" + } + } + }, + { + languageOptions: { + globals: { + bar: "writable" + } + } + } + ], { + plugins: baseConfig.plugins, + + languageOptions: { + globals: { + foo: "readonly", + bar: "writable" + } + } + })); + + it("should merge two objects when second object has overrides", () => assertMergedResult([ + { + languageOptions: { + globals: { + foo: null + } + } + }, + { + languageOptions: { + globals: { + foo: "writeable" + } + } + } + ], { + plugins: baseConfig.plugins, + + languageOptions: { + globals: { + foo: "writeable" + } + } + })); + + it("should merge an object and undefined into one object", () => assertMergedResult([ + { + languageOptions: { + globals: { + foo: "readable" + } + } + }, + { + } + ], { + plugins: baseConfig.plugins, + + languageOptions: { + globals: { + foo: "readable" + } + } + })); + + + it("should merge undefined and an object into one object", () => assertMergedResult([ + { + }, + { + languageOptions: { + globals: { + foo: "false" + } + } + } + ], { + plugins: baseConfig.plugins, + + languageOptions: { + globals: { + foo: "false" + } + } + })); + + + }); + + describe("parser", () => { + + it("should error when an unexpected value is found", async () => { + + await assertInvalidConfig([ + { + languageOptions: { + parser: true + } + } + ], "Expected an object or string."); + }); + + it("should error when an unexpected value is found", async () => { + + await assertInvalidConfig([ + { + languageOptions: { + parser: "true" + } + } + ], /Expected string in the form "pluginName\/objectName"/u); + }); + + it("should error when a plugin parser can't be found", async () => { + + await assertInvalidConfig([ + { + languageOptions: { + parser: "foo/bar" + } + } + ], "Key \"parser\": Could not find \"bar\" in plugin \"foo\"."); + }); + + it("should error when a value doesn't have a parse() method", async () => { + + await assertInvalidConfig([ + { + languageOptions: { + parser: {} + } + } + ], "Expected object to have a parse() or parseForESLint() method."); + }); + + it("should merge two objects when second object has overrides", () => { + + const parser = { parse() {} }; + const stubParser = { parse() { } }; + + return assertMergedResult([ + { + languageOptions: { + parser + } + }, + { + plugins: { + "@foo/baz": { + parsers: { + bar: stubParser + } + } + }, + languageOptions: { + parser: "@foo/baz/bar" + } + } + ], { + plugins: { + "@foo/baz": { + parsers: { + bar: stubParser + } + }, + ...baseConfig.plugins + }, + languageOptions: { + parser: stubParser + } + }); + }); + + it("should merge an object and undefined into one object", () => { + + const stubParser = { parse() { } }; + + return assertMergedResult([ + { + plugins: { + foo: { + parsers: { + bar: stubParser + } + } + }, + + languageOptions: { + parser: "foo/bar" + } + }, + { + } + ], { + plugins: { + foo: { + parsers: { + bar: stubParser + } + }, + ...baseConfig.plugins + }, + + languageOptions: { + parser: stubParser + } + }); + + }); + + + it("should merge undefined and an object into one object", () => { + + const stubParser = { parse() {} }; + + return assertMergedResult([ + { + }, + { + plugins: { + foo: { + parsers: { + bar: stubParser + } + } + }, + + languageOptions: { + parser: "foo/bar" + } + } + ], { + plugins: { + foo: { + parsers: { + bar: stubParser + } + }, + ...baseConfig.plugins + }, + + languageOptions: { + parser: stubParser + } + }); + + }); + + }); + + + describe("parserOptions", () => { + + it("should error when an unexpected value is found", async () => { + + await assertInvalidConfig([ + { + languageOptions: { + parserOptions: "true" + } + } + ], "Expected an object."); + }); + + it("should merge two objects when second object has different keys", () => assertMergedResult([ + { + languageOptions: { + parserOptions: { + foo: "whatever" + } + } + }, + { + languageOptions: { + parserOptions: { + bar: "baz" + } + } + } + ], { + plugins: baseConfig.plugins, + + languageOptions: { + parserOptions: { + foo: "whatever", + bar: "baz" + } + } + })); + + it("should deeply merge two objects when second object has different keys", () => assertMergedResult([ + { + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true + } + } + } + }, + { + languageOptions: { + parserOptions: { + ecmaFeatures: { + globalReturn: true + } + } + } + } + ], { + plugins: baseConfig.plugins, + + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + globalReturn: true + } + } + } + })); + + it("should deeply merge two objects when second object has missing key", () => assertMergedResult([ + { + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true + } + } + } + }, + { + languageOptions: { + ecmaVersion: 2021 + } + } + ], { + plugins: baseConfig.plugins, + + languageOptions: { + ecmaVersion: 2021, + parserOptions: { + ecmaFeatures: { + jsx: true + } + } + } + })); + + it("should merge two objects when second object has overrides", () => assertMergedResult([ + { + languageOptions: { + parserOptions: { + foo: "whatever" + } + } + }, + { + languageOptions: { + parserOptions: { + foo: "bar" + } + } + } + ], { + plugins: baseConfig.plugins, + + languageOptions: { + parserOptions: { + foo: "bar" + } + } + })); + + it("should merge an object and undefined into one object", () => assertMergedResult([ + { + languageOptions: { + parserOptions: { + foo: "whatever" + } + } + }, + { + } + ], { + plugins: baseConfig.plugins, + + languageOptions: { + parserOptions: { + foo: "whatever" + } + } + })); + + + it("should merge undefined and an object into one object", () => assertMergedResult([ + { + }, + { + languageOptions: { + parserOptions: { + foo: "bar" + } + } + } + ], { + plugins: baseConfig.plugins, + + languageOptions: { + parserOptions: { + foo: "bar" + } + } + })); + + + }); + + + }); + + describe("rules", () => { + + it("should error when an unexpected value is found", async () => { + + await assertInvalidConfig([ + { + rules: true + } + ], "Expected an object."); + }); + + it("should error when an invalid rule severity is set", async () => { + + await assertInvalidConfig([ + { + rules: { + foo: true + } + } + ], "Key \"rules\": Key \"foo\": Expected a string, number, or array."); + }); + + it("should error when an invalid rule severity of the right type is set", async () => { + + await assertInvalidConfig([ + { + rules: { + foo: 3 + } + } + ], "Key \"rules\": Key \"foo\": Expected severity of \"off\", 0, \"warn\", 1, \"error\", or 2."); + }); + + it("should error when an invalid rule severity is set in an array", async () => { + + await assertInvalidConfig([ + { + rules: { + foo: [true] + } + } + ], "Key \"rules\": Key \"foo\": Expected severity of \"off\", 0, \"warn\", 1, \"error\", or 2."); + }); + + it("should error when rule doesn't exist", async () => { + + await assertInvalidConfig([ + { + rules: { + foox: [1, "bar"] + } + } + ], /Key "rules": Key "foox": Could not find "foox" in plugin "@"./u); + }); + + it("should error and suggest alternative when rule doesn't exist", async () => { + + await assertInvalidConfig([ + { + rules: { + "test2/match": "error" + } + } + ], /Key "rules": Key "test2\/match": Could not find "match" in plugin "test2"\. Did you mean "test1\/match"\?/u); + }); + + it("should error when plugin for rule doesn't exist", async () => { + + await assertInvalidConfig([ + { + rules: { + "doesnt-exist/match": "error" + } + } + ], /Key "rules": Key "doesnt-exist\/match": Could not find plugin "doesnt-exist"\./u); + }); + + it("should error when rule options don't match schema", async () => { + + await assertInvalidConfig([ + { + rules: { + foo: [1, "bar"] + } + } + ], /Value "bar" should be equal to one of the allowed values/u); + }); + + it("should error when rule options don't match schema requiring at least one item", async () => { + + await assertInvalidConfig([ + { + rules: { + foo2: 1 + } + } + ], /Value \[\] should NOT have fewer than 1 items/u); + }); + + it("should merge two objects", () => assertMergedResult([ + { + rules: { + foo: 1, + bar: "error" + } + }, + { + rules: { + baz: "warn", + boom: 0 + } + } + ], { + plugins: baseConfig.plugins, + + rules: { + foo: [1], + bar: [2], + baz: [1], + boom: [0] + } + })); + + it("should merge two objects when second object has simple overrides", () => assertMergedResult([ + { + rules: { + foo: [1, "always"], + bar: "error" + } + }, + { + rules: { + foo: "error", + bar: 0 + } + } + ], { + plugins: baseConfig.plugins, + + rules: { + foo: [2, "always"], + bar: [0] + } + })); + + it("should merge two objects when second object has array overrides", () => assertMergedResult([ + { + rules: { + foo: 1, + bar: "error" + } + }, + { + rules: { + foo: ["error", "never"], + bar: ["warn", "foo"] + } + } + ], { + plugins: baseConfig.plugins, + rules: { + foo: [2, "never"], + bar: [1, "foo"] + } + })); + + it("should merge two objects and options when second object overrides without options", () => assertMergedResult([ + { + rules: { + foo: [1, "always"], + bar: "error" + } + }, + { + plugins: { + "foo/baz/boom": { + rules: { + bang: {} + } + } + }, + rules: { + foo: ["error"], + bar: 0, + "foo/baz/boom/bang": "error" + } + } + ], { + plugins: { + ...baseConfig.plugins, + "foo/baz/boom": { + rules: { + bang: {} + } + } + }, + rules: { + foo: [2, "always"], + bar: [0], + "foo/baz/boom/bang": [2] + } + })); + + it("should merge an object and undefined into one object", () => assertMergedResult([ + { + rules: { + foo: 0, + bar: 1 + } + }, + { + } + ], { + plugins: baseConfig.plugins, + rules: { + foo: [0], + bar: [1] + } + })); + + it("should merge a rule that doesn't exist without error when the rule is off", () => assertMergedResult([ + { + rules: { + foo: 0, + bar: 1 + } + }, + { + rules: { + nonExistentRule: 0, + nonExistentRule2: ["off", "bar"] + } + } + ], { + plugins: baseConfig.plugins, + rules: { + foo: [0], + bar: [1], + nonExistentRule: [0], + nonExistentRule2: [0, "bar"] + } + })); + + }); + + }); +}); diff --git a/eslint/tests/lib/eslint/eslint.js b/eslint/tests/lib/eslint/eslint.js index c7783a6..eaf4aaa 100644 --- a/eslint/tests/lib/eslint/eslint.js +++ b/eslint/tests/lib/eslint/eslint.js @@ -19,9 +19,14 @@ const fCache = require("file-entry-cache"); const sinon = require("sinon"); const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); const shell = require("shelljs"); -const { CascadingConfigArrayFactory } = require("@eslint/eslintrc/lib/cascading-config-array-factory"); +const { + Legacy: { + CascadingConfigArrayFactory + } +} = require("@eslint/eslintrc"); const hash = require("../../../lib/cli-engine/hash"); const { unIndent, createCustomTeardown } = require("../../_utils"); +const coreRules = require("../../../lib/rules"); //------------------------------------------------------------------------------ // Tests @@ -40,7 +45,7 @@ describe("ESLint", () => { const originalDir = process.cwd(); const fixtureDir = path.resolve(fs.realpathSync(os.tmpdir()), "eslint/fixtures"); - /** @type {import("../../../lib/eslint")["ESLint"]} */ + /** @type {import("../../../lib/eslint").ESLint} */ let ESLint; /** @@ -94,7 +99,7 @@ describe("ESLint", () => { * exceeds the default test timeout, so raise it just for this hook. * Mocha uses `this` to set timeouts on an individual hook level. */ - this.timeout(60 * 1000); // eslint-disable-line no-invalid-this + this.timeout(60 * 1000); // eslint-disable-line no-invalid-this -- Mocha API shell.mkdir("-p", fixtureDir); shell.cp("-r", "./tests/fixtures/.", fixtureDir); }); @@ -122,7 +127,7 @@ describe("ESLint", () => { it("should report one fatal message when given a path by --ignore-path that is not a file when ignore is true.", () => { assert.throws(() => { - // eslint-disable-next-line no-new + // eslint-disable-next-line no-new -- Check for throwing new ESLint({ ignorePath: fixtureDir }); }, new RegExp(escapeStringRegExp(`Cannot read .eslintignore file: ${fixtureDir}\nError: EISDIR: illegal operation on a directory, read`), "u")); }); @@ -131,7 +136,7 @@ describe("ESLint", () => { it("should not modify baseConfig when format is specified", () => { const customBaseConfig = { root: true }; - new ESLint({ baseConfig: customBaseConfig }); // eslint-disable-line no-new + new ESLint({ baseConfig: customBaseConfig }); // eslint-disable-line no-new -- Check for argument side effects assert.deepStrictEqual(customBaseConfig, { root: true }); }); @@ -198,7 +203,7 @@ describe("ESLint", () => { "- 'errorOnUnmatchedPattern' must be a boolean.", "- 'extensions' must be an array of non-empty strings or null.", "- 'fix' must be a boolean or a function.", - "- 'fixTypes' must be an array of any of \"problem\", \"suggestion\", and \"layout\".", + "- 'fixTypes' must be an array of any of \"directive\", \"problem\", \"suggestion\", and \"layout\".", "- 'globInputPaths' must be a boolean.", "- 'ignore' must be a boolean.", "- 'ignorePath' must be a non-empty string or null.", @@ -396,6 +401,7 @@ describe("ESLint", () => { messages: [], errorCount: 0, warningCount: 0, + fatalErrorCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, output: "var bar = foo;", @@ -441,7 +447,7 @@ describe("ESLint", () => { fix: true, fixTypes: ["layou"] }); - }, /'fixTypes' must be an array of any of "problem", "suggestion", and "layout"\./iu); + }, /'fixTypes' must be an array of any of "directive", "problem", "suggestion", and "layout"\./iu); }); it("should not fix any rules when fixTypes is used without fix", async () => { @@ -596,6 +602,7 @@ describe("ESLint", () => { ], errorCount: 1, warningCount: 0, + fatalErrorCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, source: "var bar = foo", @@ -635,6 +642,7 @@ describe("ESLint", () => { ], errorCount: 1, warningCount: 0, + fatalErrorCount: 1, fixableErrorCount: 0, fixableWarningCount: 0, output: "var bar = foothis is a syntax error.", @@ -673,6 +681,7 @@ describe("ESLint", () => { ], errorCount: 1, warningCount: 0, + fatalErrorCount: 1, fixableErrorCount: 0, fixableWarningCount: 0, source: "var bar =", @@ -760,6 +769,7 @@ describe("ESLint", () => { ], errorCount: 1, warningCount: 0, + fatalErrorCount: 1, fixableErrorCount: 0, fixableWarningCount: 0, source: "var bar = foothis is a syntax error.\n return bar;", @@ -786,7 +796,7 @@ describe("ESLint", () => { const Module = require("module"); let originalFindPath = null; - /* eslint-disable no-underscore-dangle */ + /* eslint-disable no-underscore-dangle -- Override Node API */ before(() => { originalFindPath = Module._findPath; Module._findPath = function(id, ...otherArgs) { @@ -799,7 +809,7 @@ describe("ESLint", () => { after(() => { Module._findPath = originalFindPath; }); - /* eslint-enable no-underscore-dangle */ + /* eslint-enable no-underscore-dangle -- Override Node API */ it("should resolve 'plugins:[\"@scope\"]' to 'node_modules/@scope/eslint-plugin'.", async () => { eslint = new ESLint({ cwd: getFixturePath("plugin-shorthand/basic") }); @@ -846,7 +856,7 @@ describe("ESLint", () => { it("should throw if 'options' argument contains unknown key", async () => { eslint = new ESLint(); - await assert.rejects(() => eslint.lintText("var a = 0", { filename: "foo.js" }), /'options' must not include the unknown option 'filename'/u); + await assert.rejects(() => eslint.lintText("var a = 0", { filename: "foo.js" }), /'options' must not include the unknown option\(s\): filename/u); }); it("should throw if non-string value is given to 'options.filePath' option", async () => { @@ -862,7 +872,7 @@ describe("ESLint", () => { describe("lintFiles()", () => { - /** @type {InstanceType} */ + /** @type {InstanceType} */ let eslint; it("should use correct parser when custom parser is specified", async () => { @@ -1657,6 +1667,7 @@ describe("ESLint", () => { messages: [], errorCount: 0, warningCount: 0, + fatalErrorCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, output: "true ? \"yes\" : \"no\";\n", @@ -1667,6 +1678,7 @@ describe("ESLint", () => { messages: [], errorCount: 0, warningCount: 0, + fatalErrorCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, usedDeprecatedRules: [] @@ -1688,6 +1700,7 @@ describe("ESLint", () => { ], errorCount: 1, warningCount: 0, + fatalErrorCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, output: "var msg = \"hi\";\nif (msg == \"hi\") {\n\n}\n", @@ -1710,6 +1723,7 @@ describe("ESLint", () => { ], errorCount: 1, warningCount: 0, + fatalErrorCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, output: "var msg = \"hi\" + foo;\n", @@ -2081,6 +2095,36 @@ describe("ESLint", () => { assert.strictEqual(results[0].messages[0].ruleId, "test/example-rule"); }); + it("should return two messages when executing with `baseConfig` that extends preloaded plugin config", async () => { + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + baseConfig: { + extends: ["plugin:test/preset"] + }, + plugins: { + test: { + rules: { + "example-rule": require("../../fixtures/rules/custom-rule") + }, + configs: { + preset: { + rules: { + "test/example-rule": 1 + }, + plugins: ["test"] + } + } + } + } + }); + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "test/example-rule"); + }); + it("should load plugins from the `loadPluginsRelativeTo` directory, if specified", async () => { eslint = new ESLint({ resolvePluginsRelativeTo: getFixturePath("plugins"), @@ -4297,7 +4341,7 @@ describe("ESLint", () => { assert.throws(() => { try { - // eslint-disable-next-line no-new + // eslint-disable-next-line no-new -- Check for error new ESLint({ cwd }); } catch (error) { assert.strictEqual(error.messageTemplate, "failed-to-read-json"); @@ -4323,7 +4367,7 @@ describe("ESLint", () => { const cwd = getFixturePath("ignored-paths", "bad-package-json-ignore"); assert.throws(() => { - // eslint-disable-next-line no-new + // eslint-disable-next-line no-new -- Check for throwing new ESLint({ cwd }); }, /Package\.json eslintIgnore property requires an array of paths/u); }); @@ -4452,7 +4496,7 @@ describe("ESLint", () => { const ignorePath = getFixturePath("ignored-paths", "not-a-directory", ".foobaz"); assert.throws(() => { - // eslint-disable-next-line no-new + // eslint-disable-next-line no-new -- Check for throwing new ESLint({ ignorePath, cwd }); }, /Cannot read \.eslintignore file/u); }); @@ -4652,7 +4696,7 @@ describe("ESLint", () => { assert.strictEqual(typeof formatter.format, "function"); }); - it("should throw if a customer formatter doesn't exist", async () => { + it("should throw if a custom formatter doesn't exist", async () => { const engine = new ESLint(); const formatterPath = getFixturePath("formatters", "doesntexist.js"); const fullFormatterPath = path.resolve(formatterPath); @@ -4790,6 +4834,80 @@ describe("ESLint", () => { }); }); + describe("getRulesMetaForResults()", () => { + it("should return empty object when there are no linting errors", async () => { + const engine = new ESLint({ + useEslintrc: false + }); + + const rulesMeta = engine.getRulesMetaForResults([]); + + assert.strictEqual(Object.keys(rulesMeta).length, 0); + }); + + it("should return one rule meta when there is a linting error", async () => { + const engine = new ESLint({ + useEslintrc: false, + overrideConfig: { + rules: { + semi: 2 + } + } + }); + + const results = await engine.lintText("a"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); + }); + + it("should return multiple rule meta when there are multiple linting errors", async () => { + const engine = new ESLint({ + useEslintrc: false, + overrideConfig: { + rules: { + semi: 2, + quotes: [2, "double"] + } + } + }); + + const results = await engine.lintText("'a'"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); + assert.strictEqual(rulesMeta.quotes, coreRules.get("quotes").meta); + }); + + it("should return multiple rule meta when there are multiple linting errors from a plugin", async () => { + const nodePlugin = require("eslint-plugin-node"); + const engine = new ESLint({ + useEslintrc: false, + plugins: { + node: nodePlugin + }, + overrideConfig: { + plugins: ["node"], + rules: { + "node/no-new-require": 2, + semi: 2, + quotes: [2, "double"] + } + } + }); + + const results = await engine.lintText("new require('hi')"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); + assert.strictEqual(rulesMeta.quotes, coreRules.get("quotes").meta); + assert.strictEqual( + rulesMeta["node/no-new-require"], + nodePlugin.rules["no-new-require"].meta + ); + }); + }); + describe("outputFixes()", () => { afterEach(() => { sinon.verifyAndRestore(); @@ -4864,6 +4982,7 @@ describe("ESLint", () => { ].join("\n"); const config = { ignore: true, + useEslintrc: false, allowInlineConfig: false, overrideConfig: { env: { browser: true }, @@ -4890,6 +5009,7 @@ describe("ESLint", () => { ].join("\n"); const config = { ignore: true, + useEslintrc: false, allowInlineConfig: true, overrideConfig: { env: { browser: true }, @@ -4925,13 +5045,18 @@ describe("ESLint", () => { message: "Unused eslint-disable directive (no problems were reported).", line: 1, column: 1, + fix: { + range: [0, 20], + text: " " + }, severity: 2, nodeType: null } ], errorCount: 1, warningCount: 0, - fixableErrorCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 1, fixableWarningCount: 0, source: "/* eslint-disable */", usedDeprecatedRules: [] @@ -5985,7 +6110,8 @@ describe("ESLint", () => { ], source: "a == b", usedDeprecatedRules: [], - warningCount: 0 + warningCount: 0, + fatalErrorCount: 0 } ]); }); @@ -6008,7 +6134,8 @@ describe("ESLint", () => { fixableWarningCount: 0, messages: [], usedDeprecatedRules: [], - warningCount: 0 + warningCount: 0, + fatalErrorCount: 0 } ]); }); @@ -6055,7 +6182,8 @@ describe("ESLint", () => { fixableWarningCount: 0, messages: [], usedDeprecatedRules: [], - warningCount: 0 + warningCount: 0, + fatalErrorCount: 0 } ]); }); @@ -6091,7 +6219,8 @@ describe("ESLint", () => { ], source: "a == b", usedDeprecatedRules: [], - warningCount: 0 + warningCount: 0, + fatalErrorCount: 0 } ]); }); diff --git a/eslint/tests/lib/init/config-file.js b/eslint/tests/lib/init/config-file.js index e9fe62e..50a9af1 100644 --- a/eslint/tests/lib/init/config-file.js +++ b/eslint/tests/lib/init/config-file.js @@ -60,8 +60,8 @@ describe("ConfigFile", () => { [ ["JavaScript", "foo.js", espree.parse], ["JSON", "bar.json", JSON.parse], - ["YAML", "foo.yaml", yaml.safeLoad], - ["YML", "foo.yml", yaml.safeLoad] + ["YAML", "foo.yaml", yaml.load], + ["YML", "foo.yml", yaml.load] ].forEach(([fileType, filename, validate]) => { it(`should write a file through fs when a ${fileType} path is passed`, () => { diff --git a/eslint/tests/lib/linter/apply-disable-directives.js b/eslint/tests/lib/linter/apply-disable-directives.js index 84dddcb..72708bc 100644 --- a/eslint/tests/lib/linter/apply-disable-directives.js +++ b/eslint/tests/lib/linter/apply-disable-directives.js @@ -5,15 +5,41 @@ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + const assert = require("chai").assert; const applyDisableDirectives = require("../../../lib/linter/apply-disable-directives"); +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + +/** + * Creates a ParentComment for a given range. + * @param {[number, number]} range total range of the comment + * @param {string} value String value of the comment + * @param {string[]} ruleIds Rule IDs reported in the value + * @returns {ParentComment} Test-ready ParentComment object. + */ +function createParentComment(range, value, ruleIds = []) { + return { + commentToken: { range, value }, + ruleIds + }; +} + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + describe("apply-disable-directives", () => { describe("/* eslint-disable */ comments without rules", () => { it("keeps problems before the comment on the same line", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ type: "disable", line: 1, column: 8, ruleId: null }], + directives: [{ parentComment: createParentComment([0, 7]), type: "disable", line: 1, column: 8, ruleId: null }], problems: [{ line: 1, column: 7, ruleId: "foo" }] }), [{ ruleId: "foo", line: 1, column: 7 }] @@ -23,7 +49,7 @@ describe("apply-disable-directives", () => { it("keeps problems on a previous line before the comment", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ type: "disable", line: 2, column: 8, ruleId: null }], + directives: [{ parentComment: createParentComment([21, 27]), type: "disable", line: 2, column: 1, ruleId: null }], problems: [{ line: 1, column: 10, ruleId: "foo" }] }), [{ ruleId: "foo", line: 1, column: 10 }] @@ -85,7 +111,13 @@ describe("apply-disable-directives", () => { it("keeps problems after the comment that have a different ruleId", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ type: "disable", line: 1, column: 8, ruleId: "foo" }], + directives: [{ + parentComment: createParentComment([26, 29]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo" + }], problems: [{ line: 2, column: 3, ruleId: "not-foo" }] }), [{ line: 2, column: 3, ruleId: "not-foo" }] @@ -95,7 +127,13 @@ describe("apply-disable-directives", () => { it("keeps problems before the comment that have the same ruleId", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ type: "disable", line: 1, column: 8, ruleId: "foo" }], + directives: [{ + parentComment: createParentComment([7, 31]), + type: "disable", + line: 1, + column: 8, + ruleId: "foo" + }], problems: [{ line: 1, column: 7, ruleId: "foo" }] }), [{ line: 1, column: 7, ruleId: "foo" }] @@ -108,12 +146,24 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [ - { type: "disable", line: 1, column: 1, ruleId: null }, - { type: "enable", line: 1, column: 5, ruleId: null } + { + parentComment: createParentComment([0, 26]), + type: "disable", + line: 1, + column: 1, + ruleId: null + }, + { + parentComment: createParentComment([27, 45]), + type: "enable", + line: 1, + column: 26, + ruleId: null + } ], - problems: [{ line: 1, column: 7, ruleId: "foo" }] + problems: [{ line: 1, column: 27, ruleId: "foo" }] }), - [{ line: 1, column: 7, ruleId: "foo" }] + [{ line: 1, column: 27, ruleId: "foo" }] ); }); @@ -121,12 +171,24 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [ - { type: "disable", line: 1, column: 1, ruleId: null }, - { type: "enable", line: 1, column: 5, ruleId: null } + { + parentComment: createParentComment([0, 25]), + type: "disable", + line: 1, + column: 1, + ruleId: null + }, + { + parentComment: createParentComment([26, 40]), + type: "enable", + line: 1, + column: 26, + ruleId: null + } ], - problems: [{ line: 1, column: 5, ruleId: "foo" }] + problems: [{ line: 1, column: 26, ruleId: "foo" }] }), - [{ line: 1, column: 5, ruleId: "foo" }] + [{ line: 1, column: 26, ruleId: "foo" }] ); }); @@ -135,7 +197,7 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { type: "disable", line: 1, column: 1, ruleId: null }, - { type: "enable", line: 1, column: 5, ruleId: null } + { type: "enable", line: 1, column: 26, ruleId: null } ], problems: [{ line: 1, column: 3, ruleId: "foo" }] }), @@ -147,9 +209,27 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [ - { type: "disable", line: 1, column: 1, ruleId: null }, - { type: "enable", line: 1, column: 5, ruleId: "foo" }, - { type: "disable", line: 2, column: 1, ruleId: "foo" } + { + parentComment: createParentComment([0, 20]), + type: "disable", + line: 1, + column: 1, + ruleId: null + }, + { + parentComment: createParentComment([26, 44]), + type: "enable", + line: 1, + column: 26, + ruleId: "foo" + }, + { + parentComment: createParentComment([45, 63]), + type: "disable", + line: 2, + column: 1, + ruleId: "foo" + } ], problems: [{ line: 3, column: 3, ruleId: "foo" }] }), @@ -161,9 +241,27 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [ - { type: "disable", line: 1, column: 1, ruleId: null }, - { type: "enable", line: 1, column: 5, ruleId: "foo" }, - { type: "disable", line: 2, column: 1, ruleId: null } + { + parentComment: createParentComment([0, 20]), + type: "disable", + line: 1, + column: 1, + ruleId: null + }, + { + parentComment: createParentComment([21, 44]), + type: "enable", + line: 1, + column: 26, + ruleId: "foo" + }, + { + parentComment: createParentComment([45, 63]), + type: "disable", + line: 2, + column: 1, + ruleId: null + } ], problems: [{ line: 3, column: 3, ruleId: "foo" }] }), @@ -175,8 +273,20 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [ - { type: "disable", line: 1, column: 1, ruleId: "foo" }, - { type: "enable", line: 1, column: 5, ruleId: null } + { + parentComment: createParentComment([0, 20]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo" + }, + { + parentComment: createParentComment([25, 44]), + type: "enable", + line: 1, + column: 26, + ruleId: null + } ], problems: [{ line: 1, column: 3, ruleId: "not-foo" }] }), @@ -190,8 +300,20 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [ - { type: "disable", line: 1, column: 4, ruleId: null }, - { type: "enable", line: 2, column: 1, ruleId: "foo" } + { + parentComment: createParentComment([0, 20]), + type: "disable", + line: 1, + column: 1, + ruleId: null + }, + { + parentComment: createParentComment([21, 44]), + type: "enable", + line: 2, + column: 1, + ruleId: "foo" + } ], problems: [{ line: 2, column: 4, ruleId: "foo" }] }), @@ -203,8 +325,20 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [ - { type: "disable", line: 1, column: 4, ruleId: null }, - { type: "enable", line: 2, column: 1, ruleId: "foo" } + { + parentComment: createParentComment([0, 20]), + type: "disable", + line: 1, + column: 1, + ruleId: null + }, + { + parentComment: createParentComment([21, 44]), + type: "enable", + line: 2, + column: 1, + ruleId: "foo" + } ], problems: [{ line: 2, column: 1, ruleId: "foo" }] }), @@ -216,7 +350,7 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [ - { type: "disable", line: 1, column: 4, ruleId: null }, + { type: "disable", line: 1, column: 1, ruleId: null }, { type: "enable", line: 2, column: 1, ruleId: "foo" } ], problems: [{ line: 2, column: 4, ruleId: "not-foo" }] @@ -230,22 +364,22 @@ describe("apply-disable-directives", () => { applyDisableDirectives({ directives: [ { type: "disable", line: 1, column: 1, ruleId: null }, - { type: "enable", line: 1, column: 3, ruleId: "foo" }, - { type: "enable", line: 1, column: 5, ruleId: "bar" } + { type: "enable", line: 1, column: 22, ruleId: "foo" }, + { type: "enable", line: 1, column: 46, ruleId: "bar" } ], problems: [ - { line: 1, column: 2, ruleId: "foo" }, - { line: 1, column: 2, ruleId: "bar" }, - { line: 1, column: 4, ruleId: "foo" }, - { line: 1, column: 4, ruleId: "bar" }, - { line: 1, column: 6, ruleId: "foo" }, - { line: 1, column: 6, ruleId: "bar" } + { line: 1, column: 10, ruleId: "foo" }, + { line: 1, column: 10, ruleId: "bar" }, + { line: 1, column: 30, ruleId: "foo" }, + { line: 1, column: 30, ruleId: "bar" }, + { line: 1, column: 50, ruleId: "foo" }, + { line: 1, column: 50, ruleId: "bar" } ] }), [ - { line: 1, column: 4, ruleId: "foo" }, - { line: 1, column: 6, ruleId: "foo" }, - { line: 1, column: 6, ruleId: "bar" } + { line: 1, column: 30, ruleId: "foo" }, + { line: 1, column: 50, ruleId: "foo" }, + { line: 1, column: 50, ruleId: "bar" } ] ); }); @@ -255,7 +389,13 @@ describe("apply-disable-directives", () => { it("keeps problems on a previous line", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ type: "disable-line", line: 2, column: 1, ruleId: null }], + directives: [{ + parentComment: createParentComment([6, 27]), + type: "disable-line", + line: 2, + column: 1, + ruleId: null + }], problems: [{ line: 1, column: 5, ruleId: "foo" }] }), [{ line: 1, column: 5, ruleId: "foo" }] @@ -265,7 +405,13 @@ describe("apply-disable-directives", () => { it("filters problems before the comment on the same line", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ type: "disable-line", line: 1, column: 5, ruleId: null }], + directives: [{ + parentComment: createParentComment([7, 28]), + type: "disable-line", + line: 1, + column: 8, + ruleId: null + }], problems: [{ line: 1, column: 1, ruleId: "foo" }] }), [] @@ -275,7 +421,13 @@ describe("apply-disable-directives", () => { it("filters problems after the comment on the same line", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ type: "disable-line", line: 1, column: 5, ruleId: null }], + directives: [{ + parentComment: createParentComment([7, 28]), + type: "disable-line", + line: 1, + column: 8, + ruleId: null + }], problems: [{ line: 1, column: 10, ruleId: "foo" }] }), [] @@ -285,7 +437,13 @@ describe("apply-disable-directives", () => { it("keeps problems on a following line", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ type: "disable-line", line: 1, column: 4 }], + directives: [{ + parentComment: createParentComment([7, 34]), + type: "disable-line", + line: 1, + column: 8, + ruleId: "foo" + }], problems: [{ line: 2, column: 1, ruleId: "foo" }] }), [{ line: 2, column: 1, ruleId: "foo" }] @@ -297,7 +455,13 @@ describe("apply-disable-directives", () => { it("filters problems on the current line that match the ruleId", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ type: "disable-line", line: 1, column: 4, ruleId: "foo" }], + directives: [{ + parentComment: createParentComment([7, 34]), + type: "disable-line", + line: 1, + column: 8, + ruleId: "foo" + }], problems: [{ line: 1, column: 2, ruleId: "foo" }] }), [] @@ -307,7 +471,7 @@ describe("apply-disable-directives", () => { it("keeps problems on the current line that do not match the ruleId", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ type: "disable-line", line: 1, column: 4, ruleId: "foo" }], + directives: [{ parentComment: createParentComment([0, 27]), type: "disable-line", line: 1, column: 1, ruleId: "foo" }], problems: [{ line: 1, column: 2, ruleId: "not-foo" }] }), [{ line: 1, column: 2, ruleId: "not-foo" }] @@ -318,8 +482,20 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [ - { type: "disable", line: 1, column: 1, ruleId: null }, - { type: "disable-line", line: 1, column: 3, ruleId: "foo" } + { + parentComment: createParentComment([0, 21]), + type: "disable", + line: 1, + column: 1, + ruleId: null + }, + { + parentComment: createParentComment([24, 28]), + type: "disable-line", + line: 1, + column: 22, + ruleId: "foo" + } ], problems: [{ line: 1, column: 5, ruleId: "not-foo" }] }), @@ -331,12 +507,48 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [ - { type: "disable-line", line: 1, column: 5, ruleId: "foo" }, - { type: "disable-line", line: 2, column: 5, ruleId: "foo" }, - { type: "disable-line", line: 3, column: 5, ruleId: "foo" }, - { type: "disable-line", line: 4, column: 5, ruleId: "foo" }, - { type: "disable-line", line: 5, column: 5, ruleId: "foo" }, - { type: "disable-line", line: 6, column: 5, ruleId: "foo" } + { + parentComment: createParentComment([7, 34]), + type: "disable-line", + line: 1, + column: 8, + ruleId: "foo" + }, + { + parentComment: createParentComment([38, 73]), + type: "disable-line", + line: 2, + column: 8, + ruleId: "foo" + }, + { + parentComment: createParentComment([76, 111]), + type: "disable-line", + line: 3, + column: 8, + ruleId: "foo" + }, + { + parentComment: createParentComment([114, 149]), + type: "disable-line", + line: 4, + column: 8, + ruleId: "foo" + }, + { + parentComment: createParentComment([152, 187]), + type: "disable-line", + line: 5, + column: 8, + ruleId: "foo" + }, + { + parentComment: createParentComment([190, 225]), + type: "disable-line", + line: 6, + column: 8, + ruleId: "foo" + } ], problems: [{ line: 2, column: 1, ruleId: "foo" }] }), @@ -349,7 +561,13 @@ describe("apply-disable-directives", () => { it("filters problems on the next line", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ type: "disable-next-line", line: 1, column: 1, ruleId: null }], + directives: [{ + parentComment: createParentComment([0, 31]), + type: "disable-next-line", + line: 1, + column: 1, + ruleId: null + }], problems: [{ line: 2, column: 3, ruleId: "foo" }] }), [] @@ -359,7 +577,13 @@ describe("apply-disable-directives", () => { it("keeps problems on the same line", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ type: "disable-next-line", line: 1, column: 1, ruleId: null }], + directives: [{ + parentComment: createParentComment([0, 31]), + type: "disable-next-line", + line: 1, + column: 1, + ruleId: null + }], problems: [{ line: 1, column: 3, ruleId: "foo" }] }), [{ line: 1, column: 3, ruleId: "foo" }] @@ -369,7 +593,13 @@ describe("apply-disable-directives", () => { it("keeps problems after the next line", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ type: "disable-next-line", line: 1, column: 1, ruleId: null }], + directives: [{ + parentComment: createParentComment([0, 31]), + type: "disable-next-line", + line: 1, + column: 1, + ruleId: null + }], problems: [{ line: 3, column: 3, ruleId: "foo" }] }), [{ line: 3, column: 3, ruleId: "foo" }] @@ -404,7 +634,13 @@ describe("apply-disable-directives", () => { it("keeps problems on the next line that do not match the ruleId", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ type: "disable-next-line", line: 1, column: 1, ruleId: "foo" }], + directives: [{ + parentComment: createParentComment([0, 31]), + type: "disable-next-line", + line: 1, + column: 1, + ruleId: "foo" + }], problems: [{ line: 2, column: 1, ruleId: "not-foo" }] }), [{ line: 2, column: 1, ruleId: "not-foo" }] @@ -429,7 +665,12 @@ describe("apply-disable-directives", () => { it("Adds a problem for /* eslint-disable */", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ type: "disable", line: 1, column: 5 }], + directives: [{ + parentComment: createParentComment([0, 20]), + type: "disable", + line: 1, + column: 1 + }], problems: [], reportUnusedDisableDirectives: "error" }), @@ -438,7 +679,37 @@ describe("apply-disable-directives", () => { ruleId: null, message: "Unused eslint-disable directive (no problems were reported).", line: 1, - column: 5, + column: 1, + fix: { + range: [0, 20], + text: " " + }, + severity: 2, + nodeType: null + } + ] + ); + }); + + it("Does not fix a problem for /* eslint-disable */ when disableFixes is enabled", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [{ + parentComment: createParentComment([0, 20]), + type: "disable", + line: 1, + column: 1 + }], + disableFixes: true, + problems: [], + reportUnusedDisableDirectives: "error" + }), + [ + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, severity: 2, nodeType: null } @@ -449,7 +720,7 @@ describe("apply-disable-directives", () => { it("Does not add a problem for /* eslint-disable */ /* (problem) */", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ type: "disable", line: 1, column: 5, ruleId: null }], + directives: [{ type: "disable", line: 1, column: 1, ruleId: null }], problems: [{ line: 2, column: 1, ruleId: "foo" }], reportUnusedDisableDirectives: "error" }), @@ -460,7 +731,13 @@ describe("apply-disable-directives", () => { it("Adds a problem for /* eslint-disable foo */", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ type: "disable", line: 1, column: 5, ruleId: "foo" }], + directives: [{ + parentComment: createParentComment([0, 21]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo" + }], problems: [], reportUnusedDisableDirectives: "error" }), @@ -469,7 +746,11 @@ describe("apply-disable-directives", () => { ruleId: null, message: "Unused eslint-disable directive (no problems were reported from 'foo').", line: 1, - column: 5, + column: 1, + fix: { + range: [0, 21], + text: " " + }, severity: 2, nodeType: null } @@ -480,7 +761,13 @@ describe("apply-disable-directives", () => { it("Adds a problem for /* eslint-disable foo */ /* (problem from another rule) */", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ type: "disable", line: 1, column: 5, ruleId: "foo" }], + directives: [{ + parentComment: createParentComment([0, 24]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo" + }], problems: [{ line: 1, column: 20, ruleId: "not-foo" }], reportUnusedDisableDirectives: "error" }), @@ -489,7 +776,11 @@ describe("apply-disable-directives", () => { ruleId: null, message: "Unused eslint-disable directive (no problems were reported from 'foo').", line: 1, - column: 5, + column: 1, + fix: { + range: [0, 24], + text: " " + }, severity: 2, nodeType: null }, @@ -506,8 +797,20 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [ - { type: "disable", line: 1, column: 5, ruleId: null }, - { type: "enable", line: 1, column: 6, ruleId: "foo" } + { + parentComment: createParentComment([0, 21]), + type: "disable", + line: 1, + column: 8, + ruleId: null + }, + { + parentComment: createParentComment([0, 21]), + type: "enable", + line: 1, + column: 24, + ruleId: "foo" + } ], problems: [{ line: 1, column: 2, ruleId: "foo" }], reportUnusedDisableDirectives: "error" @@ -521,8 +824,12 @@ describe("apply-disable-directives", () => { { ruleId: null, message: "Unused eslint-disable directive (no problems were reported).", + fix: { + range: [0, 21], + text: " " + }, line: 1, - column: 5, + column: 8, severity: 2, nodeType: null } @@ -534,8 +841,20 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [ - { type: "disable", line: 1, column: 5, ruleId: null }, - { type: "enable", line: 1, column: 6, ruleId: null } + { + parentComment: createParentComment([0, 20]), + type: "disable", + line: 1, + column: 1, + ruleId: null + }, + { + parentComment: createParentComment([21, 41]), + type: "enable", + line: 1, + column: 12, + ruleId: null + } ], problems: [], reportUnusedDisableDirectives: "error" @@ -545,7 +864,11 @@ describe("apply-disable-directives", () => { ruleId: null, message: "Unused eslint-disable directive (no problems were reported).", line: 1, - column: 5, + column: 1, + fix: { + range: [0, 20], + text: " " + }, severity: 2, nodeType: null } @@ -557,8 +880,20 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [ - { type: "disable", line: 1, column: 1, ruleId: null }, - { type: "disable", line: 2, column: 1, ruleId: null } + { + parentComment: createParentComment([0, 21]), + type: "disable", + line: 1, + column: 1, + ruleId: null + }, + { + parentComment: createParentComment([21, 42]), + type: "disable", + line: 2, + column: 1, + ruleId: null + } ], problems: [], reportUnusedDisableDirectives: "error" @@ -569,6 +904,10 @@ describe("apply-disable-directives", () => { message: "Unused eslint-disable directive (no problems were reported).", line: 1, column: 1, + fix: { + range: [0, 21], + text: " " + }, severity: 2, nodeType: null }, @@ -577,6 +916,10 @@ describe("apply-disable-directives", () => { message: "Unused eslint-disable directive (no problems were reported).", line: 2, column: 1, + fix: { + range: [21, 42], + text: " " + }, severity: 2, nodeType: null } @@ -588,8 +931,20 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [ - { type: "disable", line: 1, column: 1, ruleId: null }, - { type: "disable", line: 2, column: 1, ruleId: null } + { + parentComment: createParentComment([0, 21]), + type: "disable", + line: 1, + column: 1, + ruleId: null + }, + { + parentComment: createParentComment([22, 45]), + type: "disable", + line: 2, + column: 1, + ruleId: null + } ], problems: [{ line: 3, column: 1, ruleId: "foo" }], reportUnusedDisableDirectives: "error" @@ -600,6 +955,10 @@ describe("apply-disable-directives", () => { message: "Unused eslint-disable directive (no problems were reported).", line: 1, column: 1, + fix: { + range: [0, 21], + text: " " + }, severity: 2, nodeType: null } @@ -611,8 +970,20 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [ - { type: "disable", line: 1, column: 1, ruleId: "foo" }, - { type: "disable", line: 2, column: 1, ruleId: null } + { + parentComment: createParentComment([0, 21]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo" + }, + { + parentComment: createParentComment([22, 45]), + type: "disable", + line: 2, + column: 1, + ruleId: null + } ], problems: [{ line: 3, column: 1, ruleId: "foo" }], reportUnusedDisableDirectives: "error" @@ -623,6 +994,10 @@ describe("apply-disable-directives", () => { message: "Unused eslint-disable directive (no problems were reported from 'foo').", line: 1, column: 1, + fix: { + range: [0, 21], + text: " " + }, severity: 2, nodeType: null } @@ -633,7 +1008,7 @@ describe("apply-disable-directives", () => { it("Does not add a problem for /* eslint-disable foo */ /* (problem from foo) */", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ type: "disable", line: 1, column: 5, ruleId: "foo" }], + directives: [{ type: "disable", line: 1, column: 1, ruleId: "foo" }], problems: [{ line: 1, column: 6, ruleId: "foo" }], reportUnusedDisableDirectives: "error" }), @@ -645,8 +1020,20 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [ - { type: "disable", line: 1, column: 1, ruleId: null }, - { type: "disable", line: 2, column: 1, ruleId: "foo" } + { + parentComment: createParentComment([0, 21]), + type: "disable", + line: 1, + column: 1, + ruleId: null + }, + { + parentComment: createParentComment([22, 45]), + type: "disable", + line: 2, + column: 1, + ruleId: "foo" + } ], problems: [{ line: 3, column: 1, ruleId: "foo" }], reportUnusedDisableDirectives: "error" @@ -657,6 +1044,10 @@ describe("apply-disable-directives", () => { message: "Unused eslint-disable directive (no problems were reported).", line: 1, column: 1, + fix: { + range: [0, 21], + text: " " + }, severity: 2, nodeType: null } @@ -668,8 +1059,20 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [ - { type: "disable", line: 1, column: 1, ruleId: null }, - { type: "disable", line: 2, column: 1, ruleId: "foo" } + { + parentComment: createParentComment([0, 20]), + type: "disable", + line: 1, + column: 1, + ruleId: null + }, + { + parentComment: createParentComment([21, 45]), + type: "disable", + line: 2, + column: 1, + ruleId: "foo" + } ], problems: [{ line: 3, column: 1, ruleId: "bar" }], reportUnusedDisableDirectives: "error" @@ -680,6 +1083,10 @@ describe("apply-disable-directives", () => { message: "Unused eslint-disable directive (no problems were reported from 'foo').", line: 2, column: 1, + fix: { + range: [21, 45], + text: " " + }, severity: 2, nodeType: null } @@ -691,10 +1098,22 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [ - { type: "disable", line: 1, column: 5, ruleId: "foo" }, - { type: "enable", line: 1, column: 8, ruleId: "foo" } + { + parentComment: createParentComment([0, 20]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo" + }, + { + parentComment: createParentComment([25, 46]), + type: "enable", + line: 1, + column: 26, + ruleId: "foo" + } ], - problems: [{ line: 1, column: 10, ruleId: "foo" }], + problems: [{ line: 1, column: 30, ruleId: "foo" }], reportUnusedDisableDirectives: "error" }), [ @@ -702,14 +1121,18 @@ describe("apply-disable-directives", () => { ruleId: null, message: "Unused eslint-disable directive (no problems were reported from 'foo').", line: 1, - column: 5, + column: 1, + fix: { + range: [0, 20], + text: " " + }, severity: 2, nodeType: null }, { ruleId: "foo", line: 1, - column: 10 + column: 30 } ] ); @@ -719,10 +1142,22 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [ - { type: "disable", line: 1, column: 5, ruleId: "foo" }, - { type: "enable", line: 1, column: 8, ruleId: null } + { + parentComment: createParentComment([0, 24]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo" + }, + { + parentComment: createParentComment([25, 49]), + type: "enable", + line: 1, + column: 26, + ruleId: null + } ], - problems: [{ line: 1, column: 10, ruleId: "foo" }], + problems: [{ line: 1, column: 30, ruleId: "foo" }], reportUnusedDisableDirectives: "error" }), [ @@ -730,14 +1165,18 @@ describe("apply-disable-directives", () => { ruleId: null, message: "Unused eslint-disable directive (no problems were reported from 'foo').", line: 1, - column: 5, + column: 1, + fix: { + range: [0, 24], + text: " " + }, severity: 2, nodeType: null }, { ruleId: "foo", line: 1, - column: 10 + column: 30 } ] ); @@ -747,9 +1186,27 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [ - { type: "disable", line: 1, column: 1, ruleId: null }, - { type: "disable", line: 2, column: 1, ruleId: "foo" }, - { type: "enable", line: 3, column: 1, ruleId: "foo" } + { + parentComment: createParentComment([0, 21]), + type: "disable", + line: 1, + column: 1, + ruleId: null + }, + { + parentComment: createParentComment([22, 45]), + type: "disable", + line: 2, + column: 1, + ruleId: "foo" + }, + { + parentComment: createParentComment([46, 69]), + type: "enable", + line: 3, + column: 1, + ruleId: "foo" + } ], problems: [{ line: 4, column: 1, ruleId: "foo" }], reportUnusedDisableDirectives: "error" @@ -760,6 +1217,10 @@ describe("apply-disable-directives", () => { message: "Unused eslint-disable directive (no problems were reported).", line: 1, column: 1, + fix: { + range: [0, 21], + text: " " + }, severity: 2, nodeType: null }, @@ -768,8 +1229,11 @@ describe("apply-disable-directives", () => { message: "Unused eslint-disable directive (no problems were reported from 'foo').", line: 2, column: 1, + fix: { + range: [22, 45], + text: " " + }, severity: 2, - nodeType: null }, { @@ -784,7 +1248,13 @@ describe("apply-disable-directives", () => { it("Adds a problem for // eslint-disable-line", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ type: "disable-line", line: 1, column: 5, ruleId: null }], + directives: [{ + parentComment: createParentComment([0, 22]), + type: "disable-line", + line: 1, + column: 1, + ruleId: null + }], problems: [], reportUnusedDisableDirectives: "error" }), @@ -793,7 +1263,11 @@ describe("apply-disable-directives", () => { ruleId: null, message: "Unused eslint-disable directive (no problems were reported).", line: 1, - column: 5, + column: 1, + fix: { + range: [0, 22], + text: " " + }, severity: 2, nodeType: null } @@ -805,7 +1279,7 @@ describe("apply-disable-directives", () => { it("Does not add a problem for // eslint-disable-line (problem)", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ type: "disable-line", line: 1, column: 5, ruleId: null }], + directives: [{ type: "disable-line", line: 1, column: 1, ruleId: null }], problems: [{ line: 1, column: 10, ruleId: "foo" }], reportUnusedDisableDirectives: "error" }), @@ -816,7 +1290,13 @@ describe("apply-disable-directives", () => { it("Adds a problem for // eslint-disable-next-line", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ type: "disable-next-line", line: 1, column: 5, ruleId: null }], + directives: [{ + parentComment: createParentComment([0, 27]), + type: "disable-next-line", + line: 1, + column: 1, + ruleId: null + }], problems: [], reportUnusedDisableDirectives: "error" }), @@ -825,7 +1305,11 @@ describe("apply-disable-directives", () => { ruleId: null, message: "Unused eslint-disable directive (no problems were reported).", line: 1, - column: 5, + column: 1, + fix: { + range: [0, 27], + text: " " + }, severity: 2, nodeType: null } @@ -836,7 +1320,7 @@ describe("apply-disable-directives", () => { it("Does not add a problem for // eslint-disable-next-line \\n (problem)", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ type: "disable-next-line", line: 1, column: 5, ruleId: null }], + directives: [{ type: "disable-next-line", line: 1, column: 1, ruleId: null }], problems: [{ line: 2, column: 10, ruleId: "foo" }], reportUnusedDisableDirectives: "error" }), @@ -848,8 +1332,8 @@ describe("apply-disable-directives", () => { assert.deepStrictEqual( applyDisableDirectives({ directives: [ - { type: "disable", line: 1, column: 1, ruleId: null }, - { type: "disable-line", line: 1, column: 5, ruleId: null } + { parentComment: createParentComment([0, 20]), type: "disable", line: 1, column: 1, ruleId: null }, + { parentComment: createParentComment([20, 43]), type: "disable-line", line: 1, column: 22, ruleId: null } ], problems: [], reportUnusedDisableDirectives: "error" @@ -860,6 +1344,10 @@ describe("apply-disable-directives", () => { message: "Unused eslint-disable directive (no problems were reported).", line: 1, column: 1, + fix: { + range: [0, 20], + text: " " + }, severity: 2, nodeType: null }, @@ -867,7 +1355,11 @@ describe("apply-disable-directives", () => { ruleId: null, message: "Unused eslint-disable directive (no problems were reported).", line: 1, - column: 5, + column: 22, + fix: { + range: [20, 43], + text: " " + }, severity: 2, nodeType: null } @@ -878,7 +1370,7 @@ describe("apply-disable-directives", () => { it("Does not add problems when reportUnusedDisableDirectives: \"off\" is used", () => { assert.deepStrictEqual( applyDisableDirectives({ - directives: [{ type: "disable-next-line", line: 1, column: 5, ruleId: null }], + directives: [{ parentComment: createParentComment([0, 27]), type: "disable-next-line", line: 1, column: 1, ruleId: null }], problems: [], reportUnusedDisableDirectives: "off" }), @@ -886,4 +1378,397 @@ describe("apply-disable-directives", () => { ); }); }); + + describe("unused rules within directives", () => { + it("Adds a problem for /* eslint-disable used, unused */", () => { + const parentComment = createParentComment([0, 32], " eslint-disable used, unused ", ["used", "unused"]); + + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { + parentComment, + ruleId: "used", + type: "disable", + line: 1, + column: 18 + }, + { + parentComment, + ruleId: "unused", + type: "disable", + line: 1, + column: 22 + } + ], + problems: [{ line: 2, column: 1, ruleId: "used" }], + reportUnusedDisableDirectives: "error" + }), + [ + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported from 'unused').", + line: 1, + column: 22, + fix: { + range: [22, 30], + text: "" + }, + severity: 2, + nodeType: null + } + ] + ); + }); + it("Adds a problem for /* eslint-disable used , unused , -- unused and used are ok */", () => { + const parentComment = createParentComment([0, 62], " eslint-disable used , unused , -- unused and used are ok ", ["used", "unused"]); + + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { + parentComment, + ruleId: "used", + type: "disable", + line: 1, + column: 18 + }, + { + parentComment, + ruleId: "unused", + type: "disable", + line: 1, + column: 24 + } + ], + problems: [{ line: 2, column: 1, ruleId: "used" }], + reportUnusedDisableDirectives: "error" + }), + [ + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported from 'unused').", + line: 1, + column: 24, + fix: { + range: [23, 32], + text: "" + }, + severity: 2, + nodeType: null + } + ] + ); + }); + + it("Adds a problem for /* eslint-disable unused, used */", () => { + const parentComment = createParentComment([0, 32], " eslint-disable unused, used ", ["unused", "used"]); + + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { + parentComment, + ruleId: "unused", + type: "disable", + line: 1, + column: 18 + }, + { + parentComment, + ruleId: "used", + type: "disable", + line: 1, + column: 25 + } + ], + problems: [{ line: 2, column: 1, ruleId: "used" }], + reportUnusedDisableDirectives: "error" + }), + [ + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported from 'unused').", + line: 1, + column: 18, + fix: { + range: [18, 26], + text: "" + }, + severity: 2, + nodeType: null + } + ] + ); + }); + + it("Adds a problem for /* eslint-disable unused,, ,, used */", () => { + const parentComment = createParentComment([0, 37], " eslint-disable unused,, ,, used ", ["unused", "used"]); + + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { + parentComment, + ruleId: "unused", + type: "disable", + line: 1, + column: 18 + }, + { + parentComment, + ruleId: "used", + type: "disable", + line: 1, + column: 29 + } + ], + problems: [{ line: 2, column: 1, ruleId: "used" }], + reportUnusedDisableDirectives: "error" + }), + [ + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported from 'unused').", + line: 1, + column: 18, + fix: { + range: [18, 25], + text: "" + }, + severity: 2, + nodeType: null + } + ] + ); + }); + + it("Adds a problem for /* eslint-disable unused-1, unused-2, used */", () => { + const parentComment = createParentComment([0, 45], " eslint-disable unused-1, unused-2, used ", ["unused-1", "unused-2", "used"]); + + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { + parentComment, + ruleId: "unused-1", + type: "disable", + line: 1, + column: 18 + }, + { + parentComment, + ruleId: "unused-2", + type: "disable", + line: 1, + column: 28 + }, + { + parentComment, + ruleId: "used", + type: "disable", + line: 1, + column: 38 + } + ], + problems: [{ line: 2, column: 1, ruleId: "used" }], + reportUnusedDisableDirectives: "error" + }), + [ + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported from 'unused-1').", + line: 1, + column: 18, + fix: { + range: [18, 28], + text: "" + }, + severity: 2, + nodeType: null + }, + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported from 'unused-2').", + line: 1, + column: 28, + fix: { + range: [26, 36], + text: "" + }, + severity: 2, + nodeType: null + } + ] + ); + }); + + it("Adds a problem for /* eslint-disable unused-1, unused-2, used, unused-3 */", () => { + const parentComment = createParentComment([0, 55], " eslint-disable unused-1, unused-2, used, unused-3 ", ["unused-1", "unused-2", "used", "unused-3"]); + + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { + parentComment, + ruleId: "unused-1", + type: "disable", + line: 1, + column: 18 + }, + { + parentComment, + ruleId: "unused-2", + type: "disable", + line: 1, + column: 28 + }, + { + parentComment, + ruleId: "used", + type: "disable", + line: 1, + column: 38 + }, + { + parentComment, + ruleId: "unused-3", + type: "disable", + line: 1, + column: 43 + } + ], + problems: [{ line: 2, column: 1, ruleId: "used" }], + reportUnusedDisableDirectives: "error" + }), + [ + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported from 'unused-1').", + line: 1, + column: 18, + fix: { + range: [18, 28], + text: "" + }, + severity: 2, + nodeType: null + }, + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported from 'unused-2').", + line: 1, + column: 28, + fix: { + range: [26, 36], + text: "" + }, + severity: 2, + nodeType: null + }, + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported from 'unused-3').", + line: 1, + column: 43, + fix: { + range: [42, 52], + text: "" + }, + severity: 2, + nodeType: null + } + ] + ); + }); + + it("Adds a problem for /* eslint-disable unused-1, unused-2 */", () => { + const parentComment = createParentComment([0, 39], " eslint-disable unused-1, unused-2 ", ["unused-1", "unused-2"]); + + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { + parentComment, + ruleId: "unused-1", + type: "disable", + line: 1, + column: 18 + }, + { + parentComment, + ruleId: "unused-2", + type: "disable", + line: 1, + column: 28 + } + ], + problems: [], + reportUnusedDisableDirectives: "error" + }), + [ + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported from 'unused-1' or 'unused-2').", + line: 1, + column: 18, + fix: { + range: [0, 39], + text: " " + }, + severity: 2, + nodeType: null + } + ] + ); + }); + + it("Adds a problem for /* eslint-disable unused-1, unused-2, unused-3 */", () => { + const parentComment = createParentComment([0, 49], " eslint-disable unused-1, unused-2, unused-3 ", ["unused-1", "unused-2", "unused-3"]); + + assert.deepStrictEqual( + applyDisableDirectives({ + directives: [ + { + parentComment, + ruleId: "unused-1", + type: "disable", + line: 1, + column: 18 + }, + { + parentComment, + ruleId: "unused-2", + type: "disable", + line: 1, + column: 28 + }, + { + parentComment, + ruleId: "unused-3", + type: "disable", + line: 1, + column: 38 + } + ], + problems: [], + reportUnusedDisableDirectives: "error" + }), + [ + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported from 'unused-1', 'unused-2', or 'unused-3').", + line: 1, + column: 18, + fix: { + range: [0, 49], + text: " " + }, + severity: 2, + nodeType: null + } + ] + ); + }); + }); }); diff --git a/eslint/tests/lib/linter/code-path-analysis/code-path-analyzer.js b/eslint/tests/lib/linter/code-path-analysis/code-path-analyzer.js index b2356f2..0b5dd33 100644 --- a/eslint/tests/lib/linter/code-path-analysis/code-path-analyzer.js +++ b/eslint/tests/lib/linter/code-path-analysis/code-path-analyzer.js @@ -561,11 +561,11 @@ describe("CodePathAnalyzer", () => { } })); const messages = linter.verify(source, { - parserOptions: { ecmaVersion: 2021 }, + parserOptions: { ecmaVersion: 2022 }, rules: { test: 2 } }); - assert.strictEqual(messages.length, 0); + assert.strictEqual(messages.length, 0, "Unexpected linting error in code."); assert.strictEqual(actual.length, expected.length, "a count of code paths is wrong."); for (let i = 0; i < actual.length; ++i) { diff --git a/eslint/tests/lib/linter/code-path-analysis/code-path.js b/eslint/tests/lib/linter/code-path-analysis/code-path.js index 0c4fdd1..760d5b3 100644 --- a/eslint/tests/lib/linter/code-path-analysis/code-path.js +++ b/eslint/tests/lib/linter/code-path-analysis/code-path.js @@ -30,7 +30,11 @@ function parseCodePaths(code) { retv.push(codePath); } })); - linter.verify(code, { rules: { test: 2 } }); + + linter.verify(code, { + rules: { test: 2 }, + parserOptions: { ecmaVersion: "latest" } + }); return retv; } @@ -50,7 +54,7 @@ function getOrderOfTraversing(codePath, options, callback) { codePath.traverseSegments(options, (segment, controller) => { retv.push(segment.id); if (callback) { - callback(segment, controller); // eslint-disable-line node/callback-return + callback(segment, controller); // eslint-disable-line node/callback-return -- At end of inner function } }); @@ -62,9 +66,58 @@ function getOrderOfTraversing(codePath, options, callback) { //------------------------------------------------------------------------------ describe("CodePathAnalyzer", () => { + + /* + * If you need to output the code paths and DOT graph information for a + * particular piece of code, udpate and uncomment the following test and + * then run: + * DEBUG=eslint:code-path npx mocha tests/lib/linter/code-path-analysis/ + * + * All the information you need will be output to the console. + */ + /* + * it.only("test", () => { + * const codePaths = parseCodePaths("class Foo { a = () => b }"); + * }); + */ + + describe("CodePath#origin", () => { + + it("should be 'program' when code path starts at root node", () => { + const codePath = parseCodePaths("foo(); bar(); baz();")[0]; + + assert.strictEqual(codePath.origin, "program"); + }); + + it("should be 'function' when code path starts inside a function", () => { + const codePath = parseCodePaths("function foo() {}")[1]; + + assert.strictEqual(codePath.origin, "function"); + }); + + it("should be 'function' when code path starts inside an arrow function", () => { + const codePath = parseCodePaths("let foo = () => {}")[1]; + + assert.strictEqual(codePath.origin, "function"); + }); + + it("should be 'class-field-initializer' when code path starts inside a class field initializer", () => { + const codePath = parseCodePaths("class Foo { a=1; }")[1]; + + assert.strictEqual(codePath.origin, "class-field-initializer"); + }); + + it("should be 'class-static-block' when code path starts inside a class static block", () => { + const codePath = parseCodePaths("class Foo { static { this.a=1; } }")[1]; + + assert.strictEqual(codePath.origin, "class-static-block"); + }); + }); + describe(".traverseSegments()", () => { + describe("should traverse segments from the first to the end:", () => { - /* eslint-disable internal-rules/multiline-comment-style */ + /* eslint-disable internal-rules/multiline-comment-style -- Commenting out */ it("simple", () => { const codePath = parseCodePaths("foo(); bar(); baz();")[0]; const order = getOrderOfTraversing(codePath); @@ -301,6 +354,6 @@ describe("CodePathAnalyzer", () => { */ }); - /* eslint-enable internal-rules/multiline-comment-style */ + /* eslint-enable internal-rules/multiline-comment-style -- Commenting out */ }); }); diff --git a/eslint/tests/lib/linter/interpolate.js b/eslint/tests/lib/linter/interpolate.js index a705790..04e7140 100644 --- a/eslint/tests/lib/linter/interpolate.js +++ b/eslint/tests/lib/linter/interpolate.js @@ -1,8 +1,16 @@ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + const assert = require("chai").assert; const interpolate = require("../../../lib/linter/interpolate"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + describe("interpolate()", () => { it("passes through text without {{ }}", () => { const message = "This is a very important message!"; diff --git a/eslint/tests/lib/linter/linter.js b/eslint/tests/lib/linter/linter.js index e61804d..24dc4de 100644 --- a/eslint/tests/lib/linter/linter.js +++ b/eslint/tests/lib/linter/linter.js @@ -11,6 +11,7 @@ const assert = require("chai").assert, sinon = require("sinon"), + espree = require("espree"), esprima = require("esprima"), testParsers = require("../../fixtures/parsers/linter-test-parsers"); @@ -53,7 +54,7 @@ const ESLINT_ENV = "eslint-env"; describe("Linter", () => { const filename = "filename.js"; - /** @type {InstanceType} */ + /** @type {InstanceType} */ let linter; beforeEach(() => { @@ -86,7 +87,7 @@ describe("Linter", () => { assert.throws(() => { linter.verify(code, config, filename); - }, `Intentional error.\nOccurred while linting ${filename}:1`); + }, `Intentional error.\nOccurred while linting ${filename}:1\nRule: "checker"`); }); it("does not call rule listeners with a `this` value", () => { @@ -3202,7 +3203,7 @@ var a = "test2"; "eslint-enable eqeqeq", "eslint-env es6" ]) { - // eslint-disable-next-line no-loop-func + // eslint-disable-next-line no-loop-func -- No closures it(`should warn '/* ${directive} */' if 'noInlineConfig' was given.`, () => { const messages = linter.verify(`/* ${directive} */`, { noInlineConfig: true }); @@ -3218,7 +3219,7 @@ var a = "test2"; "eslint-disable-line eqeqeq", "eslint-disable-next-line eqeqeq" ]) { - // eslint-disable-next-line no-loop-func + // eslint-disable-next-line no-loop-func -- No closures it(`should warn '// ${directive}' if 'noInlineConfig' was given.`, () => { const messages = linter.verify(`// ${directive}`, { noInlineConfig: true }); @@ -3299,6 +3300,10 @@ var a = "test2"; message: "Unused eslint-disable directive (no problems were reported).", line: 1, column: 1, + fix: { + range: [0, 20], + text: " " + }, severity: 2, nodeType: null } @@ -3315,6 +3320,10 @@ var a = "test2"; message: "Unused eslint-disable directive (no problems were reported).", line: 1, column: 1, + fix: { + range: [0, 20], + text: " " + }, severity: 2, nodeType: null } @@ -3331,6 +3340,10 @@ var a = "test2"; message: "Unused eslint-disable directive (no problems were reported).", line: 1, column: 1, + fix: { + range: [0, 20], + text: " " + }, severity: 1, nodeType: null } @@ -3347,12 +3360,608 @@ var a = "test2"; message: "Unused eslint-disable directive (no problems were reported).", line: 1, column: 1, + fix: { + range: [0, 20], + text: " " + }, severity: 1, nodeType: null } ] ); }); + + it("reports problems for partially unused eslint-disable comments (in config)", () => { + const code = "alert('test'); // eslint-disable-line no-alert, no-redeclare"; + const config = { + reportUnusedDisableDirectives: true, + rules: { + "no-alert": 1, + "no-redeclare": 1 + } + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: true + }); + + assert.deepStrictEqual( + messages, + [ + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported from 'no-redeclare').", + line: 1, + column: 16, + fix: { + range: [46, 60], + text: "" + }, + severity: 1, + nodeType: null + } + ] + ); + }); + + describe("autofix", () => { + const alwaysReportsRule = { + create(context) { + return { + Program(node) { + context.report({ message: "bad code", loc: node.loc.end }); + } + }; + } + }; + + const neverReportsRule = { + create() { + return {}; + } + }; + + const ruleCount = 3; + const usedRules = Array.from( + { length: ruleCount }, + (_, index) => `used${index ? `-${index}` : ""}` // "used", "used-1", "used-2" + ); + const unusedRules = usedRules.map(name => `un${name}`); // "unused", "unused-1", "unused-2" + + const config = { + reportUnusedDisableDirectives: true, + rules: { + ...Object.fromEntries(usedRules.map(name => [name, "error"])), + ...Object.fromEntries(unusedRules.map(name => [name, "error"])) + } + }; + + beforeEach(() => { + linter.defineRules(Object.fromEntries(usedRules.map(name => [name, alwaysReportsRule]))); + linter.defineRules(Object.fromEntries(unusedRules.map(name => [name, neverReportsRule]))); + }); + + const tests = [ + + //----------------------------------------------- + // Removing the entire comment + //----------------------------------------------- + + { + code: "// eslint-disable-line unused", + output: " " + }, + { + code: "foo// eslint-disable-line unused", + output: "foo " + }, + { + code: "// eslint-disable-line ,unused,", + output: " " + }, + { + code: "// eslint-disable-line unused-1, unused-2", + output: " " + }, + { + code: "// eslint-disable-line ,unused-1,, unused-2,, -- comment", + output: " " + }, + { + code: "// eslint-disable-next-line unused\n", + output: " \n" + }, + { + code: "// eslint-disable-next-line unused\nfoo", + output: " \nfoo" + }, + { + code: "/* eslint-disable \nunused\n*/", + output: " " + }, + + //----------------------------------------------- + // Removing only individual rules + //----------------------------------------------- + + // content before the first rule should not be changed + { + code: "//eslint-disable-line unused, used", + output: "//eslint-disable-line used" + }, + { + code: "// eslint-disable-line unused, used", + output: "// eslint-disable-line used" + }, + { + code: "// eslint-disable-line unused, used", + output: "// eslint-disable-line used" + }, + { + code: "/*\neslint-disable unused, used*/", + output: "/*\neslint-disable used*/" + }, + { + code: "/*\n eslint-disable unused, used*/", + output: "/*\n eslint-disable used*/" + }, + { + code: "/*\r\neslint-disable unused, used*/", + output: "/*\r\neslint-disable used*/" + }, + { + code: "/*\u2028eslint-disable unused, used*/", + output: "/*\u2028eslint-disable used*/" + }, + { + code: "/*\u00A0eslint-disable unused, used*/", + output: "/*\u00A0eslint-disable used*/" + }, + { + code: "// eslint-disable-line unused, used", + output: "// eslint-disable-line used" + }, + { + code: "/* eslint-disable\nunused, used*/", + output: "/* eslint-disable\nused*/" + }, + { + code: "/* eslint-disable\n unused, used*/", + output: "/* eslint-disable\n used*/" + }, + { + code: "/* eslint-disable\r\nunused, used*/", + output: "/* eslint-disable\r\nused*/" + }, + { + code: "/* eslint-disable\u2028unused, used*/", + output: "/* eslint-disable\u2028used*/" + }, + { + code: "/* eslint-disable\u00A0unused, used*/", + output: "/* eslint-disable\u00A0used*/" + }, + + // when removing the first rule, the comma and all whitespace up to the next rule (or next lone comma) should also be removed + { + code: "// eslint-disable-line unused,used", + output: "// eslint-disable-line used" + }, + { + code: "// eslint-disable-line unused, used", + output: "// eslint-disable-line used" + }, + { + code: "// eslint-disable-line unused , used", + output: "// eslint-disable-line used" + }, + { + code: "// eslint-disable-line unused, used", + output: "// eslint-disable-line used" + }, + { + code: "// eslint-disable-line unused ,used", + output: "// eslint-disable-line used" + }, + { + code: "/* eslint-disable unused\n,\nused */", + output: "/* eslint-disable used */" + }, + { + code: "/* eslint-disable unused \n \n,\n\n used */", + output: "/* eslint-disable used */" + }, + { + code: "/* eslint-disable unused\u2028,\u2028used */", + output: "/* eslint-disable used */" + }, + { + code: "// eslint-disable-line unused\u00A0,\u00A0used", + output: "// eslint-disable-line used" + }, + { + code: "// eslint-disable-line unused,,used", + output: "// eslint-disable-line ,used" + }, + { + code: "// eslint-disable-line unused, ,used", + output: "// eslint-disable-line ,used" + }, + { + code: "// eslint-disable-line unused,, used", + output: "// eslint-disable-line , used" + }, + { + code: "// eslint-disable-line unused,used ", + output: "// eslint-disable-line used " + }, + { + code: "// eslint-disable-next-line unused,used\n", + output: "// eslint-disable-next-line used\n" + }, + + // when removing a rule in the middle, one comma and all whitespace between commas should also be removed + { + code: "// eslint-disable-line used-1,unused,used-2", + output: "// eslint-disable-line used-1,used-2" + }, + { + code: "// eslint-disable-line used-1, unused,used-2", + output: "// eslint-disable-line used-1,used-2" + }, + { + code: "// eslint-disable-line used-1,unused ,used-2", + output: "// eslint-disable-line used-1,used-2" + }, + { + code: "// eslint-disable-line used-1, unused ,used-2", + output: "// eslint-disable-line used-1,used-2" + }, + { + code: "/* eslint-disable used-1,\nunused\n,used-2 */", + output: "/* eslint-disable used-1,used-2 */" + }, + { + code: "/* eslint-disable used-1,\n\n unused \n \n ,used-2 */", + output: "/* eslint-disable used-1,used-2 */" + }, + { + code: "/* eslint-disable used-1,\u2028unused\u2028,used-2 */", + output: "/* eslint-disable used-1,used-2 */" + }, + { + code: "// eslint-disable-line used-1,\u00A0unused\u00A0,used-2", + output: "// eslint-disable-line used-1,used-2" + }, + + // when removing a rule in the middle, content around commas should not be changed + { + code: "// eslint-disable-line used-1, unused ,used-2", + output: "// eslint-disable-line used-1,used-2" + }, + { + code: "// eslint-disable-line used-1,unused, used-2", + output: "// eslint-disable-line used-1, used-2" + }, + { + code: "// eslint-disable-line used-1 ,unused,used-2", + output: "// eslint-disable-line used-1 ,used-2" + }, + { + code: "// eslint-disable-line used-1 ,unused, used-2", + output: "// eslint-disable-line used-1 , used-2" + }, + { + code: "// eslint-disable-line used-1 , unused , used-2", + output: "// eslint-disable-line used-1 , used-2" + }, + { + code: "/* eslint-disable used-1\n,unused,\nused-2 */", + output: "/* eslint-disable used-1\n,\nused-2 */" + }, + { + code: "/* eslint-disable used-1\u2028,unused,\u2028used-2 */", + output: "/* eslint-disable used-1\u2028,\u2028used-2 */" + }, + { + code: "// eslint-disable-line used-1\u00A0,unused,\u00A0used-2", + output: "// eslint-disable-line used-1\u00A0,\u00A0used-2" + }, + { + code: "// eslint-disable-line , unused ,used", + output: "// eslint-disable-line ,used" + }, + { + code: "/* eslint-disable\n, unused ,used */", + output: "/* eslint-disable\n,used */" + }, + { + code: "/* eslint-disable used-1,\n,unused,used-2 */", + output: "/* eslint-disable used-1,\n,used-2 */" + }, + { + code: "/* eslint-disable used-1,unused,\n,used-2 */", + output: "/* eslint-disable used-1,\n,used-2 */" + }, + { + code: "/* eslint-disable used-1,\n,unused,\n,used-2 */", + output: "/* eslint-disable used-1,\n,\n,used-2 */" + }, + { + code: "// eslint-disable-line used, unused,", + output: "// eslint-disable-line used," + }, + { + code: "// eslint-disable-next-line used, unused,\n", + output: "// eslint-disable-next-line used,\n" + }, + { + code: "// eslint-disable-line used, unused, ", + output: "// eslint-disable-line used, " + }, + { + code: "// eslint-disable-line used, unused, -- comment", + output: "// eslint-disable-line used, -- comment" + }, + { + code: "/* eslint-disable used, unused,\n*/", + output: "/* eslint-disable used,\n*/" + }, + + // when removing the last rule, the comma and all whitespace up to the previous rule (or previous lone comma) should also be removed + { + code: "// eslint-disable-line used,unused", + output: "// eslint-disable-line used" + }, + { + code: "// eslint-disable-line used, unused", + output: "// eslint-disable-line used" + }, + { + code: "// eslint-disable-line used ,unused", + output: "// eslint-disable-line used" + }, + { + code: "// eslint-disable-line used , unused", + output: "// eslint-disable-line used" + }, + { + code: "// eslint-disable-line used, unused", + output: "// eslint-disable-line used" + }, + { + code: "// eslint-disable-line used ,unused", + output: "// eslint-disable-line used" + }, + { + code: "/* eslint-disable used\n,\nunused */", + output: "/* eslint-disable used */" + }, + { + code: "/* eslint-disable used \n \n,\n\n unused */", + output: "/* eslint-disable used */" + }, + { + code: "/* eslint-disable used\u2028,\u2028unused */", + output: "/* eslint-disable used */" + }, + { + code: "// eslint-disable-line used\u00A0,\u00A0unused", + output: "// eslint-disable-line used" + }, + { + code: "// eslint-disable-line used,,unused", + output: "// eslint-disable-line used," + }, + { + code: "// eslint-disable-line used, ,unused", + output: "// eslint-disable-line used," + }, + { + code: "/* eslint-disable used,\n,unused */", + output: "/* eslint-disable used, */" + }, + { + code: "/* eslint-disable used\n, ,unused */", + output: "/* eslint-disable used\n, */" + }, + + // content after the last rule should not be changed + { + code: "// eslint-disable-line used,unused", + output: "// eslint-disable-line used" + }, + { + code: "// eslint-disable-line used,unused ", + output: "// eslint-disable-line used " + }, + { + code: "// eslint-disable-line used,unused ", + output: "// eslint-disable-line used " + }, + { + code: "// eslint-disable-line used,unused -- comment", + output: "// eslint-disable-line used -- comment" + }, + { + code: "// eslint-disable-next-line used,unused\n", + output: "// eslint-disable-next-line used\n" + }, + { + code: "// eslint-disable-next-line used,unused \n", + output: "// eslint-disable-next-line used \n" + }, + { + code: "/* eslint-disable used,unused\u2028*/", + output: "/* eslint-disable used\u2028*/" + }, + { + code: "// eslint-disable-line used,unused\u00A0", + output: "// eslint-disable-line used\u00A0" + }, + + // multiply rules to remove + { + code: "// eslint-disable-line used, unused-1, unused-2", + output: "// eslint-disable-line used" + }, + { + code: "// eslint-disable-line unused-1, used, unused-2", + output: "// eslint-disable-line used" + }, + { + code: "// eslint-disable-line unused-1, unused-2, used", + output: "// eslint-disable-line used" + }, + { + code: "// eslint-disable-line used-1, unused-1, used-2, unused-2", + output: "// eslint-disable-line used-1, used-2" + }, + { + code: "// eslint-disable-line unused-1, used-1, unused-2, used-2", + output: "// eslint-disable-line used-1, used-2" + }, + { + code: ` + /* eslint-disable unused-1, + used-1, + unused-2, + used-2 + */ + `, + output: ` + /* eslint-disable used-1, + used-2 + */ + ` + }, + { + code: ` + /* eslint-disable + unused-1, + used-1, + unused-2, + used-2 + */ + `, + output: ` + /* eslint-disable + used-1, + used-2 + */ + ` + }, + { + code: ` + /* eslint-disable + used-1, + unused-1, + used-2, + unused-2 + */ + `, + output: ` + /* eslint-disable + used-1, + used-2 + */ + ` + }, + { + code: ` + /* eslint-disable + used-1, + unused-1, + used-2, + unused-2, + */ + `, + output: ` + /* eslint-disable + used-1, + used-2, + */ + ` + }, + { + code: ` + /* eslint-disable + ,unused-1 + ,used-1 + ,unused-2 + ,used-2 + */ + `, + output: ` + /* eslint-disable + ,used-1 + ,used-2 + */ + ` + }, + { + code: ` + /* eslint-disable + ,used-1 + ,unused-1 + ,used-2 + ,unused-2 + */ + `, + output: ` + /* eslint-disable + ,used-1 + ,used-2 + */ + ` + }, + { + code: ` + /* eslint-disable + used-1, + unused-1, + used-2, + unused-2 + + -- comment + */ + `, + output: ` + /* eslint-disable + used-1, + used-2 + + -- comment + */ + ` + }, + + // duplicates in the list + { + code: "// eslint-disable-line unused, unused, used", + output: "// eslint-disable-line used" + }, + { + code: "// eslint-disable-line unused, used, unused", + output: "// eslint-disable-line used" + }, + { + code: "// eslint-disable-line used, unused, unused, used", + output: "// eslint-disable-line used, used" + } + ]; + + for (const { code, output } of tests) { + // eslint-disable-next-line no-loop-func -- `linter` is getting updated in beforeEach() + it(code, () => { + assert.strictEqual( + linter.verifyAndFix(code, config).output, + output + ); + }); + } + }); }); describe("when evaluating code with comments to change config when allowInlineConfig is disabled", () => { @@ -3401,7 +4010,6 @@ var a = "test2"; return {}; }); - linter.defineRule("checker", filenameChecker); linter.defineRule("checker", filenameChecker); linter.verify("foo;", { rules: { checker: "error" } }, { filename: "foo.js" }); assert(filenameChecker.calledOnce); @@ -3493,6 +4101,77 @@ var a = "test2"; }); describe("ecmaVersion", () => { + + it("should not support ES6 when no ecmaVersion provided", () => { + const messages = linter.verify("let x = 0;"); + + assert.strictEqual(messages.length, 1); + }); + + it("supports ECMAScript version 'latest'", () => { + const messages = linter.verify("let x = 5 ** 7;", { + parserOptions: { ecmaVersion: "latest" } + }); + + assert.strictEqual(messages.length, 0); + }); + + it("the 'latest' is equal to espree.lastEcmaVersion", () => { + let ecmaVersion = null; + const config = { rules: { "ecma-version": 2 }, parserOptions: { ecmaVersion: "latest" } }; + + linter.defineRule("ecma-version", context => ({ + Program() { + ecmaVersion = context.parserOptions.ecmaVersion; + } + })); + linter.verify("", config); + assert.strictEqual(ecmaVersion, espree.latestEcmaVersion); + }); + + it("should pass normalized ecmaVersion to eslint-scope", () => { + let blockScope = null; + + linter.defineRule("block-scope", context => ({ + BlockStatement() { + blockScope = context.getScope(); + } + })); + linter.defineParser("custom-parser", { + parse: (...args) => espree.parse(...args) + }); + + // Use standard parser + linter.verify("{}", { + rules: { "block-scope": 2 }, + parserOptions: { ecmaVersion: "latest" } + }); + + assert.strictEqual(blockScope.type, "block"); + + linter.verify("{}", { + rules: { "block-scope": 2 }, + parserOptions: {} // ecmaVersion defaults to 5 + }); + assert.strictEqual(blockScope.type, "global"); + + // Use custom parser + linter.verify("{}", { + rules: { "block-scope": 2 }, + parser: "custom-parser", + parserOptions: { ecmaVersion: "latest" } + }); + + assert.strictEqual(blockScope.type, "block"); + + linter.verify("{}", { + rules: { "block-scope": 2 }, + parser: "custom-parser", + parserOptions: {} // ecmaVersion defaults to 5 + }); + assert.strictEqual(blockScope.type, "global"); + }); + describe("it should properly parse let declaration when", () => { it("the ECMAScript version number is 6", () => { const messages = linter.verify("let x = 5;", { @@ -4748,21 +5427,24 @@ var a = "test2"; describe("suggestions", () => { it("provides suggestion information for tools to use", () => { - linter.defineRule("rule-with-suggestions", context => ({ - Program(node) { - context.report({ - node, - message: "Incorrect spacing", - suggest: [{ - desc: "Insert space at the beginning", - fix: fixer => fixer.insertTextBefore(node, " ") - }, { - desc: "Insert space at the end", - fix: fixer => fixer.insertTextAfter(node, " ") - }] - }); - } - })); + linter.defineRule("rule-with-suggestions", { + meta: { hasSuggestions: true }, + create: context => ({ + Program(node) { + context.report({ + node, + message: "Incorrect spacing", + suggest: [{ + desc: "Insert space at the beginning", + fix: fixer => fixer.insertTextBefore(node, " ") + }, { + desc: "Insert space at the end", + fix: fixer => fixer.insertTextAfter(node, " ") + }] + }); + } + }) + }); const messages = linter.verify("var a = 1;", { rules: { "rule-with-suggestions": "error" } }); @@ -4787,7 +5469,8 @@ var a = "test2"; messages: { suggestion1: "Insert space at the beginning", suggestion2: "Insert space at the end" - } + }, + hasSuggestions: true }, create: context => ({ Program(node) { @@ -4824,6 +5507,44 @@ var a = "test2"; } }]); }); + + it("should throw an error if suggestion is passed but `meta.hasSuggestions` property is not enabled", () => { + linter.defineRule("rule-with-suggestions", { + meta: { docs: {}, schema: [] }, + create: context => ({ + Program(node) { + context.report({ + node, + message: "hello world", + suggest: [{ desc: "convert to foo", fix: fixer => fixer.insertTextBefore(node, " ") }] + }); + } + }) + }); + + assert.throws(() => { + linter.verify("0", { rules: { "rule-with-suggestions": "error" } }); + }, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`."); + }); + + it("should throw an error if suggestion is passed but `meta.hasSuggestions` property is not enabled and the rule has the obsolete `meta.docs.suggestion` property", () => { + linter.defineRule("rule-with-meta-docs-suggestion", { + meta: { docs: { suggestion: true }, schema: [] }, + create: context => ({ + Program(node) { + context.report({ + node, + message: "hello world", + suggest: [{ desc: "convert to foo", fix: fixer => fixer.insertTextBefore(node, " ") }] + }); + } + }) + }); + + assert.throws(() => { + linter.verify("0", { rules: { "rule-with-meta-docs-suggestion": "error" } }); + }, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`. `meta.docs.suggestion` is ignored by ESLint."); + }); }); describe("mutability", () => { @@ -5011,17 +5732,24 @@ var a = "test2"; it("should use postprocessed problem ranges when applying autofixes", () => { const code = "foo bar baz"; - linter.defineRule("capitalize-identifiers", context => ({ - Identifier(node) { - if (node.name !== node.name.toUpperCase()) { - context.report({ - node, - message: "Capitalize this identifier", - fix: fixer => fixer.replaceText(node, node.name.toUpperCase()) - }); - } + linter.defineRule("capitalize-identifiers", { + meta: { + fixable: "code" + }, + create(context) { + return { + Identifier(node) { + if (node.name !== node.name.toUpperCase()) { + context.report({ + node, + message: "Capitalize this identifier", + fix: fixer => fixer.replaceText(node, node.name.toUpperCase()) + }); + } + } + }; } - })); + }); const fixResult = linter.verifyAndFix( code, @@ -5110,15 +5838,23 @@ var a = "test2"; }); it("stops fixing after 10 passes", () => { - linter.defineRule("add-spaces", context => ({ - Program(node) { - context.report({ - node, - message: "Add a space before this node.", - fix: fixer => fixer.insertTextBefore(node, " ") - }); + + linter.defineRule("add-spaces", { + meta: { + fixable: "whitespace" + }, + create(context) { + return { + Program(node) { + context.report({ + node, + message: "Add a space before this node.", + fix: fixer => fixer.insertTextBefore(node, " ") + }); + } + }; } - })); + }); const fixResult = linter.verifyAndFix("a", { rules: { "add-spaces": "error" } }); @@ -5142,10 +5878,10 @@ var a = "test2"; assert.throws(() => { linter.verify("0", { rules: { "test-rule": "error" } }); - }, /Fixable rules should export a `meta\.fixable` property.\nOccurred while linting :1$/u); + }, /Fixable rules must set the `meta\.fixable` property to "code" or "whitespace".\nOccurred while linting :1\nRule: "test-rule"$/u); }); - it("should not throw an error if fix is passed and there is no metadata", () => { + it("should throw an error if fix is passed and there is no metadata", () => { linter.defineRule("test-rule", { create: context => ({ Program(node) { @@ -5154,7 +5890,21 @@ var a = "test2"; }) }); - linter.verify("0", { rules: { "test-rule": "error" } }); + assert.throws(() => { + linter.verify("0", { rules: { "test-rule": "error" } }); + }, /Fixable rules must set the `meta\.fixable` property/u); + }); + + it("should throw an error if fix is passed from a legacy-format rule", () => { + linter.defineRule("test-rule", context => ({ + Program(node) { + context.report(node, "hello world", {}, () => ({ range: [1, 1], text: "" })); + } + })); + + assert.throws(() => { + linter.verify("0", { rules: { "test-rule": "error" } }); + }, /Fixable rules must set the `meta\.fixable` property/u); }); }); @@ -5391,7 +6141,7 @@ var a = "test2"; it("eslint-scope should use the visitorKeys (so 'childVisitorKeys.ClassDeclaration' includes 'experimentalDecorators')", () => { assert.deepStrictEqual( - scopeManager.__options.childVisitorKeys.ClassDeclaration, // eslint-disable-line no-underscore-dangle + scopeManager.__options.childVisitorKeys.ClassDeclaration, // eslint-disable-line no-underscore-dangle -- ScopeManager API ["experimentalDecorators", "id", "superClass", "body"] ); }); @@ -5470,4 +6220,25 @@ var a = "test2"; assert.strictEqual(messages.length, 0); }); }); + + describe("merging 'parserOptions'", () => { + it("should deeply merge 'parserOptions' from an environment with 'parserOptions' from the provided config", () => { + const code = "return
"; + const config = { + env: { + node: true // ecmaFeatures: { globalReturn: true } + }, + parserOptions: { + ecmaFeatures: { + jsx: true + } + } + }; + + const messages = linter.verify(code, config); + + // no parsing errors + assert.strictEqual(messages.length, 0); + }); + }); }); diff --git a/eslint/tests/lib/linter/node-event-generator.js b/eslint/tests/lib/linter/node-event-generator.js index f9010c3..44f8e51 100644 --- a/eslint/tests/lib/linter/node-event-generator.js +++ b/eslint/tests/lib/linter/node-event-generator.js @@ -17,8 +17,9 @@ const assert = require("assert"), createEmitter = require("../../../lib/linter/safe-emitter"), NodeEventGenerator = require("../../../lib/linter/node-event-generator"); + //------------------------------------------------------------------------------ -// Tests +// Constants //------------------------------------------------------------------------------ const ESPREE_CONFIG = { @@ -31,6 +32,10 @@ const ESPREE_CONFIG = { const STANDARD_ESQUERY_OPTION = { visitorKeys: vk.KEYS, fallback: Traverser.getKeys }; +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + describe("NodeEventGenerator", () => { EventGeneratorTester.testEventGeneratorInterface( new NodeEventGenerator(createEmitter(), STANDARD_ESQUERY_OPTION) @@ -252,6 +257,20 @@ describe("NodeEventGenerator", () => { ] ); + assertEmissions( + "function foo(){} var x; (function (p){}); () => {};", + [":function", "ExpressionStatement > :function", "VariableDeclaration, :function[params.length=1]"], + ast => [ + [":function", ast.body[0]], // function foo(){} + ["VariableDeclaration, :function[params.length=1]", ast.body[1]], // var x; + [":function", ast.body[2].expression], // function (p){} + ["ExpressionStatement > :function", ast.body[2].expression], // function (p){} + ["VariableDeclaration, :function[params.length=1]", ast.body[2].expression], // function (p){} + [":function", ast.body[3].expression], // () => {} + ["ExpressionStatement > :function", ast.body[3].expression] // () => {} + ] + ); + assertEmissions( "foo;", [ @@ -309,6 +328,15 @@ describe("NodeEventGenerator", () => { ["[name.length=3]:exit", ast.body[1].expression] ] ); + + // https://github.com/eslint/eslint/issues/14799 + assertEmissions( + "const {a = 1} = b;", + ["Property > .key"], + ast => [ + ["Property > .key", ast.body[0].declarations[0].id.properties[0].key] + ] + ); }); describe("traversing the entire non-standard AST", () => { diff --git a/eslint/tests/lib/linter/report-translator.js b/eslint/tests/lib/linter/report-translator.js index c6dd242..6feabb3 100644 --- a/eslint/tests/lib/linter/report-translator.js +++ b/eslint/tests/lib/linter/report-translator.js @@ -368,6 +368,35 @@ describe("createReportTranslator", () => { ); }); + it("should respect ranges of empty insertions when merging fixes to one.", () => { + const reportDescriptor = { + node, + loc: location, + message, + *fix() { + yield { range: [4, 5], text: "cd" }; + yield { range: [2, 2], text: "" }; + yield { range: [7, 7], text: "" }; + } + }; + + assert.deepStrictEqual( + translateReport(reportDescriptor), + { + ruleId: "foo-rule", + severity: 2, + message: "foo", + line: 2, + column: 1, + nodeType: "ExpressionStatement", + fix: { + range: [2, 7], + text: "o\ncdar" + } + } + ); + }); + it("should pass through fixes if only one is present", () => { const reportDescriptor = { node, @@ -1037,7 +1066,7 @@ describe("createReportTranslator", () => { for (const badRange of [[0], [0, null], [null, 0], [void 0, 1], [0, void 0], [void 0, void 0], []]) { assert.throws( - // eslint-disable-next-line no-loop-func + // eslint-disable-next-line no-loop-func -- Using arrow functions () => translateReport( { node, messageId: "testMessage", fix: () => ({ range: badRange, text: "foo" }) } ), @@ -1045,7 +1074,7 @@ describe("createReportTranslator", () => { ); assert.throws( - // eslint-disable-next-line no-loop-func + // eslint-disable-next-line no-loop-func -- Using arrow functions () => translateReport( { node, diff --git a/eslint/tests/lib/linter/rule-fixer.js b/eslint/tests/lib/linter/rule-fixer.js index a70e735..7cc350b 100644 --- a/eslint/tests/lib/linter/rule-fixer.js +++ b/eslint/tests/lib/linter/rule-fixer.js @@ -30,6 +30,17 @@ describe("RuleFixer", () => { }); + it("should allow inserting empty text", () => { + + const result = ruleFixer.insertTextBefore({ range: [10, 20] }, ""); + + assert.deepStrictEqual(result, { + range: [10, 10], + text: "" + }); + + }); + }); describe("insertTextBeforeRange", () => { @@ -45,6 +56,17 @@ describe("RuleFixer", () => { }); + it("should allow inserting empty text", () => { + + const result = ruleFixer.insertTextBeforeRange([10, 20], ""); + + assert.deepStrictEqual(result, { + range: [10, 10], + text: "" + }); + + }); + }); describe("insertTextAfter", () => { @@ -60,6 +82,17 @@ describe("RuleFixer", () => { }); + it("should allow inserting empty text", () => { + + const result = ruleFixer.insertTextAfter({ range: [10, 20] }, ""); + + assert.deepStrictEqual(result, { + range: [20, 20], + text: "" + }); + + }); + }); describe("insertTextAfterRange", () => { @@ -75,6 +108,17 @@ describe("RuleFixer", () => { }); + it("should allow inserting empty text", () => { + + const result = ruleFixer.insertTextAfterRange([10, 20], ""); + + assert.deepStrictEqual(result, { + range: [20, 20], + text: "" + }); + + }); + }); describe("removeAfter", () => { diff --git a/eslint/tests/lib/linter/safe-emitter.js b/eslint/tests/lib/linter/safe-emitter.js index e76e1a0..0808b4c 100644 --- a/eslint/tests/lib/linter/safe-emitter.js +++ b/eslint/tests/lib/linter/safe-emitter.js @@ -5,9 +5,17 @@ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + const createEmitter = require("../../../lib/linter/safe-emitter"); const assert = require("chai").assert; +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + describe("safe-emitter", () => { describe("emit() and on()", () => { it("allows listeners to be registered calls them when emitted", () => { @@ -32,7 +40,7 @@ describe("safe-emitter", () => { let called = false; emitter.on("foo", function() { - assert.strictEqual(this, void 0); // eslint-disable-line no-invalid-this + assert.strictEqual(this, void 0); // eslint-disable-line no-invalid-this -- Checking `this` value called = true; }); diff --git a/eslint/tests/lib/linter/timing.js b/eslint/tests/lib/linter/timing.js index e105234..c8c08d0 100644 --- a/eslint/tests/lib/linter/timing.js +++ b/eslint/tests/lib/linter/timing.js @@ -1,8 +1,16 @@ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + const { getListSize } = require("../../../lib/linter/timing"); const assert = require("chai").assert; +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + describe("timing", () => { describe("getListSize()", () => { after(() => { diff --git a/eslint/tests/lib/rule-tester/no-test-runners.js b/eslint/tests/lib/rule-tester/no-test-runners.js index d6b26d3..8e5785c 100644 --- a/eslint/tests/lib/rule-tester/no-test-runners.js +++ b/eslint/tests/lib/rule-tester/no-test-runners.js @@ -1,10 +1,10 @@ +/* eslint no-global-assign: off -- Resetting Mocha globals */ /** * @fileoverview Tests for RuleTester without any test runner * @author Weijia Wang */ "use strict"; -/* eslint-disable no-global-assign*/ const assert = require("assert"); const { RuleTester } = require("../../../lib/rule-tester"); const tmpIt = it; diff --git a/eslint/tests/lib/rule-tester/rule-tester.js b/eslint/tests/lib/rule-tester/rule-tester.js index 3aaf6f9..6ebd82f 100644 --- a/eslint/tests/lib/rule-tester/rule-tester.js +++ b/eslint/tests/lib/rule-tester/rule-tester.js @@ -11,7 +11,8 @@ const sinon = require("sinon"), EventEmitter = require("events"), { RuleTester } = require("../../../lib/rule-tester"), assert = require("chai").assert, - nodeAssert = require("assert"); + nodeAssert = require("assert"), + espree = require("espree"); const NODE_ASSERT_STRICT_EQUAL_OPERATOR = (() => { try { @@ -97,6 +98,266 @@ describe("RuleTester", () => { ruleTester = new RuleTester(); }); + describe("only", () => { + describe("`itOnly` accessor", () => { + describe("when `itOnly` is set", () => { + before(() => { + RuleTester.itOnly = sinon.spy(); + }); + after(() => { + RuleTester.itOnly = void 0; + }); + beforeEach(() => { + RuleTester.itOnly.resetHistory(); + ruleTester = new RuleTester(); + }); + + it("is called by exclusive tests", () => { + ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { + valid: [{ + code: "const notVar = 42;", + only: true + }], + invalid: [] + }); + + sinon.assert.calledWith(RuleTester.itOnly, "const notVar = 42;"); + }); + }); + + describe("when `it` is set and has an `only()` method", () => { + before(() => { + RuleTester.it.only = () => {}; + sinon.spy(RuleTester.it, "only"); + }); + after(() => { + RuleTester.it.only = void 0; + }); + beforeEach(() => { + RuleTester.it.only.resetHistory(); + ruleTester = new RuleTester(); + }); + + it("is called by tests with `only` set", () => { + ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { + valid: [{ + code: "const notVar = 42;", + only: true + }], + invalid: [] + }); + + sinon.assert.calledWith(RuleTester.it.only, "const notVar = 42;"); + }); + }); + + describe("when global `it` is a function that has an `only()` method", () => { + let originalGlobalItOnly; + + before(() => { + + /* + * We run tests with `--forbid-only`, so we have to override + * `it.only` to prevent the real one from being called. + */ + originalGlobalItOnly = it.only; + it.only = () => {}; + sinon.spy(it, "only"); + }); + after(() => { + it.only = originalGlobalItOnly; + }); + beforeEach(() => { + it.only.resetHistory(); + ruleTester = new RuleTester(); + }); + + it("is called by tests with `only` set", () => { + ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { + valid: [{ + code: "const notVar = 42;", + only: true + }], + invalid: [] + }); + + sinon.assert.calledWith(it.only, "const notVar = 42;"); + }); + }); + + describe("when `describe` and `it` are overridden without `itOnly`", () => { + let originalGlobalItOnly; + + before(() => { + + /* + * These tests override `describe` and `it` already, so we + * don't need to override them here. We do, however, need to + * remove `only` from the global `it` to prevent it from + * being used instead. + */ + originalGlobalItOnly = it.only; + it.only = void 0; + }); + after(() => { + it.only = originalGlobalItOnly; + }); + beforeEach(() => { + ruleTester = new RuleTester(); + }); + + it("throws an error recommending overriding `itOnly`", () => { + assert.throws(() => { + ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { + valid: [{ + code: "const notVar = 42;", + only: true + }], + invalid: [] + }); + }, "Set `RuleTester.itOnly` to use `only` with a custom test framework."); + }); + }); + + describe("when global `it` is a function that does not have an `only()` method", () => { + let originalGlobalIt; + let originalRuleTesterDescribe; + let originalRuleTesterIt; + + before(() => { + originalGlobalIt = global.it; + + // eslint-disable-next-line no-global-assign -- Temporarily override Mocha global + it = () => {}; + + /* + * These tests override `describe` and `it`, so we need to + * un-override them here so they won't interfere. + */ + originalRuleTesterDescribe = RuleTester.describe; + RuleTester.describe = void 0; + originalRuleTesterIt = RuleTester.it; + RuleTester.it = void 0; + }); + after(() => { + + // eslint-disable-next-line no-global-assign -- Restore Mocha global + it = originalGlobalIt; + RuleTester.describe = originalRuleTesterDescribe; + RuleTester.it = originalRuleTesterIt; + }); + beforeEach(() => { + ruleTester = new RuleTester(); + }); + + it("throws an error explaining that the current test framework does not support `only`", () => { + assert.throws(() => { + ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { + valid: [{ + code: "const notVar = 42;", + only: true + }], + invalid: [] + }); + }, "The current test framework does not support exclusive tests with `only`."); + }); + }); + }); + + describe("test cases", () => { + const ruleName = "no-var"; + const rule = require("../../fixtures/testers/rule-tester/no-var"); + + let originalRuleTesterIt; + let spyRuleTesterIt; + let originalRuleTesterItOnly; + let spyRuleTesterItOnly; + + before(() => { + originalRuleTesterIt = RuleTester.it; + spyRuleTesterIt = sinon.spy(); + RuleTester.it = spyRuleTesterIt; + originalRuleTesterItOnly = RuleTester.itOnly; + spyRuleTesterItOnly = sinon.spy(); + RuleTester.itOnly = spyRuleTesterItOnly; + }); + after(() => { + RuleTester.it = originalRuleTesterIt; + RuleTester.itOnly = originalRuleTesterItOnly; + }); + beforeEach(() => { + spyRuleTesterIt.resetHistory(); + spyRuleTesterItOnly.resetHistory(); + ruleTester = new RuleTester(); + }); + + it("isn't called for normal tests", () => { + ruleTester.run(ruleName, rule, { + valid: ["const notVar = 42;"], + invalid: [] + }); + sinon.assert.calledWith(spyRuleTesterIt, "const notVar = 42;"); + sinon.assert.notCalled(spyRuleTesterItOnly); + }); + + it("calls it or itOnly for every test case", () => { + + /* + * `RuleTester` doesn't implement test case exclusivity itself. + * Setting `only: true` just causes `RuleTester` to call + * whatever `only()` function is provided by the test framework + * instead of the regular `it()` function. + */ + + ruleTester.run(ruleName, rule, { + valid: [ + "const valid = 42;", + { + code: "const onlyValid = 42;", + only: true + } + ], + invalid: [ + { + code: "var invalid = 42;", + errors: [/^Bad var/u] + }, + { + code: "var onlyInvalid = 42;", + errors: [/^Bad var/u], + only: true + } + ] + }); + + sinon.assert.calledWith(spyRuleTesterIt, "const valid = 42;"); + sinon.assert.calledWith(spyRuleTesterItOnly, "const onlyValid = 42;"); + sinon.assert.calledWith(spyRuleTesterIt, "var invalid = 42;"); + sinon.assert.calledWith(spyRuleTesterItOnly, "var onlyInvalid = 42;"); + }); + }); + + describe("static helper wrapper", () => { + it("adds `only` to string test cases", () => { + const test = RuleTester.only("const valid = 42;"); + + assert.deepStrictEqual(test, { + code: "const valid = 42;", + only: true + }); + }); + + it("adds `only` to object test cases", () => { + const test = RuleTester.only({ code: "const valid = 42;" }); + + assert.deepStrictEqual(test, { + code: "const valid = 42;", + only: true + }); + }); + }); + }); + it("should not throw an error when everything passes", () => { ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { valid: [ @@ -781,6 +1042,203 @@ describe("RuleTester", () => { }); assert.strictEqual(spy.args[1][1].parser, require.resolve("esprima")); }); + it("should pass normalized ecmaVersion to the rule", () => { + const reportEcmaVersionRule = { + meta: { + messages: { + ecmaVersionMessage: "context.parserOptions.ecmaVersion is {{type}} {{ecmaVersion}}." + } + }, + create: context => ({ + Program(node) { + const { ecmaVersion } = context.parserOptions; + + context.report({ + node, + messageId: "ecmaVersionMessage", + data: { type: typeof ecmaVersion, ecmaVersion } + }); + } + }) + }; + + const notEspree = require.resolve("../../fixtures/parsers/empty-program-parser"); + + ruleTester.run("report-ecma-version", reportEcmaVersionRule, { + valid: [], + invalid: [ + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }] + }, + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }], + parserOptions: {} + }, + { + code: "
", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }], + parserOptions: { ecmaFeatures: { jsx: true } } + }, + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }], + parser: require.resolve("espree") + }, + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }], + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }], + env: { browser: true } + }, + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }], + env: { es6: false } + }, + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }], + env: { es6: true } + }, + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "8" } }], + env: { es6: false, es2017: true } + }, + { + code: "let x", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }], + env: { es6: "truthy" } + }, + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "8" } }], + env: { es2017: true } + }, + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "11" } }], + env: { es2020: true } + }, + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "12" } }], + env: { es2021: true } + }, + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }], + parserOptions: { ecmaVersion: "latest" } + }, + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }], + parser: require.resolve("espree"), + parserOptions: { ecmaVersion: "latest" } + }, + { + code: "
", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }], + parserOptions: { ecmaVersion: "latest", ecmaFeatures: { jsx: true } } + }, + { + code: "import 'foo'", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }], + parserOptions: { ecmaVersion: "latest", sourceType: "module" } + }, + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }], + parserOptions: { ecmaVersion: "latest" }, + env: { es6: true } + }, + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }], + parserOptions: { ecmaVersion: "latest" }, + env: { es2020: true } + }, + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }], + parser: notEspree + }, + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }], + parser: notEspree, + parserOptions: {} + }, + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "5" } }], + parser: notEspree, + parserOptions: { ecmaVersion: 5 } + }, + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }], + parser: notEspree, + parserOptions: { ecmaVersion: 6 } + }, + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }], + parser: notEspree, + parserOptions: { ecmaVersion: 2015 } + }, + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "string", ecmaVersion: "latest" } }], + parser: notEspree, + parserOptions: { ecmaVersion: "latest" } + } + ] + }); + + [{ parserOptions: { ecmaVersion: 6 } }, { env: { es6: true } }].forEach(options => { + new RuleTester(options).run("report-ecma-version", reportEcmaVersionRule, { + valid: [], + invalid: [ + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }] + }, + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }], + parserOptions: {} + } + ] + }); + }); + + new RuleTester({ parser: notEspree }).run("report-ecma-version", reportEcmaVersionRule, { + valid: [], + invalid: [ + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }] + }, + { + code: "", + errors: [{ messageId: "ecmaVersionMessage", data: { type: "string", ecmaVersion: "latest" } }], + parserOptions: { ecmaVersion: "latest" } + } + ] + }); + }); it("should pass-through services from parseForESLint to the rule", () => { const enhancedParserPath = require.resolve("../../fixtures/parsers/enhanced-parser"); @@ -1289,7 +1747,7 @@ describe("RuleTester", () => { { code: "var foo = bar;", output: "5", errors: 1 } ] }); - }, "Fixable rules should export a `meta.fixable` property."); + }, /Fixable rules must set the `meta\.fixable` property/u); }); it("should throw an error if a legacy-format rule produces fixes", () => { @@ -1313,7 +1771,7 @@ describe("RuleTester", () => { { code: "var foo = bar;", output: "5", errors: 1 } ] }); - }, "Fixable rules should export a `meta.fixable` property."); + }, /Fixable rules must set the `meta\.fixable` property/u); }); describe("suggestions", () => { @@ -1821,6 +2279,17 @@ describe("RuleTester", () => { }); }, /Invalid suggestion property name 'outpt'/u); }); + + it("should throw an error if a rule that doesn't have `meta.hasSuggestions` enabled produces suggestions", () => { + assert.throws(() => { + ruleTester.run("suggestions-missing-hasSuggestions-property", require("../../fixtures/testers/rule-tester/suggestions").withoutHasSuggestionsProperty, { + valid: [], + invalid: [ + { code: "var foo = bar;", output: "5", errors: 1 } + ] + }); + }, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`."); + }); }); describe("naming test cases", () => { @@ -1829,8 +2298,8 @@ describe("RuleTester", () => { * Asserts that a particular value will be emitted from an EventEmitter. * @param {EventEmitter} emitter The emitter that should emit a value * @param {string} emitType The type of emission to listen for - * @param {*} expectedValue The value that should be emitted - * @returns {Promise} A Promise that fulfills if the value is emitted, and rejects if something else is emitted. + * @param {any} expectedValue The value that should be emitted + * @returns {Promise} A Promise that fulfills if the value is emitted, and rejects if something else is emitted. * The Promise will be indefinitely pending if no value is emitted. */ function assertEmitted(emitter, emitType, expectedValue) { @@ -1916,6 +2385,59 @@ describe("RuleTester", () => { return assertion; }); + + it('should use the "name" property if set to a non-empty string', () => { + const assertion = assertEmitted(ruleTesterTestEmitter, "it", "my test"); + + ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { + valid: [], + invalid: [ + { + name: "my test", + code: "var x = invalid(code);", + output: " x = invalid(code);", + errors: 1 + } + ] + }); + + return assertion; + }); + + it('should use the "name" property if set to a non-empty string for valid cases too', () => { + const assertion = assertEmitted(ruleTesterTestEmitter, "it", "my test"); + + ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { + valid: [ + { + name: "my test", + code: "valid(code);" + } + ], + invalid: [] + }); + + return assertion; + }); + + + it('should use the test code as the name if the "name" property is set to an empty string', () => { + const assertion = assertEmitted(ruleTesterTestEmitter, "it", "var x = invalid(code);"); + + ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { + valid: [], + invalid: [ + { + name: "", + code: "var x = invalid(code);", + output: " x = invalid(code);", + errors: 1 + } + ] + }); + + return assertion; + }); }); // https://github.com/eslint/eslint/issues/11615 @@ -1923,17 +2445,24 @@ describe("RuleTester", () => { assert.throw(() => { ruleTester.run( "foo", - context => ({ - Identifier(node) { - context.report({ - node, - message: "make a syntax error", - fix(fixer) { - return fixer.replaceText(node, "one two"); + { + meta: { + fixable: "code" + }, + create(context) { + return { + Identifier(node) { + context.report({ + node, + message: "make a syntax error", + fix(fixer) { + return fixer.replaceText(node, "one two"); + } + }); } - }); + }; } - }), + }, { valid: ["one()"], invalid: [] @@ -2004,4 +2533,37 @@ describe("RuleTester", () => { }); + describe("SourceCode#getComments()", () => { + const useGetCommentsRule = { + create: context => ({ + Program(node) { + const sourceCode = context.getSourceCode(); + + sourceCode.getComments(node); + } + }) + }; + + it("should throw if called from a valid test case", () => { + assert.throws(() => { + ruleTester.run("use-get-comments", useGetCommentsRule, { + valid: [""], + invalid: [] + }); + }, /`SourceCode#getComments\(\)` is deprecated/u); + }); + + it("should throw if called from an invalid test case", () => { + assert.throws(() => { + ruleTester.run("use-get-comments", useGetCommentsRule, { + valid: [], + invalid: [{ + code: "", + errors: [{}] + }] + }); + }, /`SourceCode#getComments\(\)` is deprecated/u); + }); + }); + }); diff --git a/eslint/tests/lib/rules/accessor-pairs.js b/eslint/tests/lib/rules/accessor-pairs.js index c634132..d74913f 100644 --- a/eslint/tests/lib/rules/accessor-pairs.js +++ b/eslint/tests/lib/rules/accessor-pairs.js @@ -335,6 +335,11 @@ ruleTester.run("accessor-pairs", rule, { options: [{ enforceForClassMembers: true }], parserOptions: { ecmaVersion: 6 } }, + { + code: "class A { get #a() {} }", + options: [{ enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 13 } + }, // Explicitly disabled option { @@ -1207,6 +1212,26 @@ ruleTester.run("accessor-pairs", rule, { parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Getter is not present for class static setter 'a'.", type: "MethodDefinition" }] }, + { + code: "class A { set '#a'(foo) {} }", + parserOptions: { ecmaVersion: 13 }, + errors: [{ message: "Getter is not present for class setter '#a'.", type: "MethodDefinition" }] + }, + { + code: "class A { set #a(foo) {} }", + parserOptions: { ecmaVersion: 13 }, + errors: [{ message: "Getter is not present for class private setter #a.", type: "MethodDefinition" }] + }, + { + code: "class A { static set '#a'(foo) {} }", + parserOptions: { ecmaVersion: 13 }, + errors: [{ message: "Getter is not present for class static setter '#a'.", type: "MethodDefinition" }] + }, + { + code: "class A { static set #a(foo) {} }", + parserOptions: { ecmaVersion: 13 }, + errors: [{ message: "Getter is not present for class static private setter #a.", type: "MethodDefinition" }] + }, // Test that the accessor kind options do not affect each other { @@ -1239,6 +1264,30 @@ ruleTester.run("accessor-pairs", rule, { parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Setter is not present for class getter 'a'.", type: "MethodDefinition" }] }, + { + code: "class A { get '#a'() {} };", + options: [{ setWithoutGet: false, getWithoutSet: true, enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 13 }, + errors: [{ message: "Setter is not present for class getter '#a'.", type: "MethodDefinition" }] + }, + { + code: "class A { get #a() {} };", + options: [{ setWithoutGet: false, getWithoutSet: true, enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 13 }, + errors: [{ message: "Setter is not present for class private getter #a.", type: "MethodDefinition" }] + }, + { + code: "class A { static get '#a'() {} };", + options: [{ setWithoutGet: false, getWithoutSet: true, enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 13 }, + errors: [{ message: "Setter is not present for class static getter '#a'.", type: "MethodDefinition" }] + }, + { + code: "class A { static get #a() {} };", + options: [{ setWithoutGet: false, getWithoutSet: true, enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 13 }, + errors: [{ message: "Setter is not present for class static private getter #a.", type: "MethodDefinition" }] + }, // Various kinds of keys { @@ -1424,6 +1473,24 @@ ruleTester.run("accessor-pairs", rule, { { message: "Getter is not present for class setter.", type: "MethodDefinition", column: 28 } ] }, + { + code: "class A { get #a() {} set '#a'(foo) {} }", + options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 13 }, + errors: [ + { message: "Setter is not present for class private getter #a.", type: "MethodDefinition", column: 11 }, + { message: "Getter is not present for class setter '#a'.", type: "MethodDefinition", column: 23 } + ] + }, + { + code: "class A { get '#a'() {} set #a(foo) {} }", + options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 13 }, + errors: [ + { message: "Setter is not present for class getter '#a'.", type: "MethodDefinition", column: 11 }, + { message: "Getter is not present for class private setter #a.", type: "MethodDefinition", column: 25 } + ] + }, // Prototype and static accessors with same keys { diff --git a/eslint/tests/lib/rules/array-callback-return.js b/eslint/tests/lib/rules/array-callback-return.js index 6d6a4d8..13a0112 100644 --- a/eslint/tests/lib/rules/array-callback-return.js +++ b/eslint/tests/lib/rules/array-callback-return.js @@ -12,6 +12,10 @@ const rule = require("../../../lib/rules/array-callback-return"), { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester(); const allowImplicitOptions = [{ allowImplicit: true }]; diff --git a/eslint/tests/lib/rules/arrow-parens.js b/eslint/tests/lib/rules/arrow-parens.js index 61d7635..d2f32a7 100644 --- a/eslint/tests/lib/rules/arrow-parens.js +++ b/eslint/tests/lib/rules/arrow-parens.js @@ -18,6 +18,7 @@ const parser = baseParser.bind(null, "arrow-parens"); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ + const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } }); const valid = [ diff --git a/eslint/tests/lib/rules/block-scoped-var.js b/eslint/tests/lib/rules/block-scoped-var.js index 596c3ba..f401332 100644 --- a/eslint/tests/lib/rules/block-scoped-var.js +++ b/eslint/tests/lib/rules/block-scoped-var.js @@ -110,7 +110,15 @@ ruleTester.run("block-scoped-var", rule, { // https://github.com/eslint/eslint/issues/2967 "(function () { foo(); })(); function foo() {}", - { code: "(function () { foo(); })(); function foo() {}", parserOptions: { ecmaVersion: 6, sourceType: "module" } } + { code: "(function () { foo(); })(); function foo() {}", parserOptions: { ecmaVersion: 6, sourceType: "module" } }, + + { code: "class C { static { var foo; foo; } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo; var foo; } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { if (bar) { foo; } var foo; } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "var foo; class C { static { foo; } } ", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo; } } var foo;", parserOptions: { ecmaVersion: 2022 } }, + { code: "var foo; class C { static {} [foo]; } ", parserOptions: { ecmaVersion: 2022 } }, + { code: "foo; class C { static {} } var foo; ", parserOptions: { ecmaVersion: 2022 } } ], invalid: [ { code: "function f(){ x; { var x; } }", errors: [{ messageId: "outOfScope", data: { name: "x" }, type: "Identifier" }] }, @@ -165,6 +173,11 @@ ruleTester.run("block-scoped-var", rule, { { messageId: "outOfScope", data: { name: "i" }, type: "Identifier" }, { messageId: "outOfScope", data: { name: "i" }, type: "Identifier" } ] + }, + { + code: "class C { static { if (bar) { var foo; } foo; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "outOfScope", data: { name: "foo" }, type: "Identifier" }] } ] }); diff --git a/eslint/tests/lib/rules/block-spacing.js b/eslint/tests/lib/rules/block-spacing.js index a80d07c..0dfef7e 100644 --- a/eslint/tests/lib/rules/block-spacing.js +++ b/eslint/tests/lib/rules/block-spacing.js @@ -42,6 +42,9 @@ ruleTester.run("block-spacing", rule, { { code: "(() => { bar(); });", parserOptions: { ecmaVersion: 6 } }, "if (a) { /* comment */ foo(); /* comment */ }", "if (a) { //comment\n foo(); }", + { code: "class C { static {} }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo; } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { /* comment */foo;/* comment */ } }", parserOptions: { ecmaVersion: 2022 } }, // never { code: "{foo();}", options: ["never"] }, @@ -62,7 +65,13 @@ ruleTester.run("block-spacing", rule, { { code: "(function() {bar();});", options: ["never"] }, { code: "(() => {bar();});", options: ["never"], parserOptions: { ecmaVersion: 6 } }, { code: "if (a) {/* comment */ foo(); /* comment */}", options: ["never"] }, - { code: "if (a) { //comment\n foo();}", options: ["never"] } + { code: "if (a) { //comment\n foo();}", options: ["never"] }, + { code: "class C { static { } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static {foo;} }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static {/* comment */ foo; /* comment */} }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { // line comment is allowed\n foo;\n} }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static {\nfoo;\n} }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { \n foo; \n } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -292,6 +301,170 @@ ruleTester.run("block-spacing", rule, { ] }, + // class static blocks + { + code: "class C { static {foo; } }", + output: "class C { static { foo; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + type: "StaticBlock", + messageId: "missing", + data: { + location: "after", + token: "{" + }, + line: 1, + column: 18, + endLine: 1, + endColumn: 19 + } + ] + }, + { + code: "class C { static { foo;} }", + output: "class C { static { foo; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + type: "StaticBlock", + messageId: "missing", + data: { + location: "before", + token: "}" + }, + line: 1, + column: 24, + endLine: 1, + endColumn: 25 + } + ] + }, + { + code: "class C { static {foo;} }", + output: "class C { static { foo; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + type: "StaticBlock", + messageId: "missing", + data: { + location: "after", + token: "{" + }, + line: 1, + column: 18, + endLine: 1, + endColumn: 19 + }, + { + type: "StaticBlock", + messageId: "missing", + data: { + location: "before", + token: "}" + }, + line: 1, + column: 23, + endLine: 1, + endColumn: 24 + } + ] + }, + { + code: "class C { static {/* comment */} }", + output: "class C { static { /* comment */ } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + type: "StaticBlock", + messageId: "missing", + data: { + location: "after", + token: "{" + }, + line: 1, + column: 18, + endLine: 1, + endColumn: 19 + }, + { + type: "StaticBlock", + messageId: "missing", + data: { + location: "before", + token: "}" + }, + line: 1, + column: 32, + endLine: 1, + endColumn: 33 + } + ] + }, + { + code: "class C { static {/* comment 1 */ foo; /* comment 2 */} }", + output: "class C { static { /* comment 1 */ foo; /* comment 2 */ } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + type: "StaticBlock", + messageId: "missing", + data: { + location: "after", + token: "{" + }, + line: 1, + column: 18, + endLine: 1, + endColumn: 19 + }, + { + type: "StaticBlock", + messageId: "missing", + data: { + location: "before", + token: "}" + }, + line: 1, + column: 55, + endLine: 1, + endColumn: 56 + } + ] + }, + { + code: "class C {\n static {foo()\nbar()} }", + output: "class C {\n static { foo()\nbar() } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + type: "StaticBlock", + messageId: "missing", + data: { + location: "after", + token: "{" + }, + line: 2, + column: 9, + endLine: 2, + endColumn: 10 + }, + { + type: "StaticBlock", + messageId: "missing", + data: { + location: "before", + token: "}" + }, + line: 3, + column: 6, + endLine: 3, + endColumn: 7 + } + ] + }, + //---------------------------------------------------------------------- // never { @@ -817,6 +990,176 @@ ruleTester.run("block-spacing", rule, { endColumn: 21 } ] + }, + + // class static blocks + { + code: "class C { static { foo;} }", + output: "class C { static {foo;} }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + type: "StaticBlock", + messageId: "extra", + data: { + location: "after", + token: "{" + }, + line: 1, + column: 19, + endLine: 1, + endColumn: 20 + } + ] + }, + { + code: "class C { static {foo; } }", + output: "class C { static {foo;} }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + type: "StaticBlock", + messageId: "extra", + data: { + location: "before", + token: "}" + }, + line: 1, + column: 23, + endLine: 1, + endColumn: 24 + } + ] + }, + { + code: "class C { static { foo; } }", + output: "class C { static {foo;} }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + type: "StaticBlock", + messageId: "extra", + data: { + location: "after", + token: "{" + }, + line: 1, + column: 19, + endLine: 1, + endColumn: 20 + }, + { + type: "StaticBlock", + messageId: "extra", + data: { + location: "before", + token: "}" + }, + line: 1, + column: 24, + endLine: 1, + endColumn: 25 + } + ] + }, + { + code: "class C { static { /* comment */ } }", + output: "class C { static {/* comment */} }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + type: "StaticBlock", + messageId: "extra", + data: { + location: "after", + token: "{" + }, + line: 1, + column: 19, + endLine: 1, + endColumn: 20 + }, + { + type: "StaticBlock", + messageId: "extra", + data: { + location: "before", + token: "}" + }, + line: 1, + column: 33, + endLine: 1, + endColumn: 34 + } + ] + }, + { + code: "class C { static { /* comment 1 */ foo; /* comment 2 */ } }", + output: "class C { static {/* comment 1 */ foo; /* comment 2 */} }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + type: "StaticBlock", + messageId: "extra", + data: { + location: "after", + token: "{" + }, + line: 1, + column: 19, + endLine: 1, + endColumn: 20 + }, + { + type: "StaticBlock", + messageId: "extra", + data: { + location: "before", + token: "}" + }, + line: 1, + column: 56, + endLine: 1, + endColumn: 57 + } + ] + }, + { + code: "class C { static\n{ foo()\nbar() } }", + output: "class C { static\n{foo()\nbar()} }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + type: "StaticBlock", + messageId: "extra", + data: { + location: "after", + token: "{" + }, + line: 2, + column: 2, + endLine: 2, + endColumn: 5 + }, + { + type: "StaticBlock", + messageId: "extra", + data: { + location: "before", + token: "}" + }, + line: 3, + column: 6, + endLine: 3, + endColumn: 8 + } + ] } ] }); diff --git a/eslint/tests/lib/rules/brace-style.js b/eslint/tests/lib/rules/brace-style.js index 7021160..9629f42 100644 --- a/eslint/tests/lib/rules/brace-style.js +++ b/eslint/tests/lib/rules/brace-style.js @@ -10,7 +10,8 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/brace-style"), - { RuleTester } = require("../../../lib/rule-tester"); + { RuleTester } = require("../../../lib/rule-tester"), + { unIndent } = require("../../_utils"); //------------------------------------------------------------------------------ // Tests @@ -204,7 +205,125 @@ ruleTester.run("brace-style", rule, { ` if (foo) bar = function() {} else baz() - ` + `, + + // class static blocks + { + code: unIndent` + class C { + static { + foo; + } + } + `, + options: ["1tbs"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C { + static {} + + static { + } + } + `, + options: ["1tbs"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C { + static { foo; } + } + `, + options: ["1tbs", { allowSingleLine: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C { + static { + foo; + } + } + `, + options: ["stroustrup"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C { + static {} + + static { + } + } + `, + options: ["stroustrup"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C { + static { foo; } + } + `, + options: ["stroustrup", { allowSingleLine: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C + { + static + { + foo; + } + } + `, + options: ["allman"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C + { + static + {} + } + `, + options: ["allman"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C + { + static {} + + static { foo; } + + static + { foo; } + } + `, + options: ["allman", { allowSingleLine: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C { + static { + { + foo; + } + } + } + `, + options: ["1tbs"], + parserOptions: { ecmaVersion: 2022 } + } ], invalid: [ @@ -664,6 +783,334 @@ ruleTester.run("brace-style", rule, { { messageId: "nextLineOpen", type: "Punctuator" }, { messageId: "nextLineClose", type: "Punctuator" } ] + }, + + /* + * class static blocks + * + * Note about the autofix: this rule only inserts linebreaks and removes linebreaks. + * It does not aim to produce code with a valid indentation. Indentation and other formatting issues + * are expected to be fixed by `indent` and other rules in subsequent iterations. + */ + { + code: unIndent` + class C { + static + { + foo; + } + } + `, + output: unIndent` + class C { + static { + foo; + } + } + `, + options: ["1tbs"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "nextLineOpen", type: "Punctuator" } + ] + }, + { + code: unIndent` + class C { + static {foo; + } + } + `, + output: unIndent` + class C { + static { + foo; + } + } + `, + options: ["1tbs"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "blockSameLine", type: "Punctuator" } + ] + }, + { + code: unIndent` + class C { + static { + foo;} + } + `, + output: unIndent` + class C { + static { + foo; + } + } + `, + options: ["1tbs"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "singleLineClose", type: "Punctuator" } + ] + }, + { + code: unIndent` + class C { + static + {foo;} + } + `, + output: unIndent` + class C { + static { + foo; + } + } + `, + options: ["1tbs"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "nextLineOpen", type: "Punctuator" }, + { messageId: "blockSameLine", type: "Punctuator" }, + { messageId: "singleLineClose", type: "Punctuator" } + ] + }, + { + code: unIndent` + class C { + static + {} + } + `, + output: unIndent` + class C { + static {} + } + `, + options: ["1tbs"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "nextLineOpen", type: "Punctuator" } + ] + }, + { + code: unIndent` + class C { + static + { + foo; + } + } + `, + output: unIndent` + class C { + static { + foo; + } + } + `, + options: ["stroustrup"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "nextLineOpen", type: "Punctuator" } + ] + }, + { + code: unIndent` + class C { + static {foo; + } + } + `, + output: unIndent` + class C { + static { + foo; + } + } + `, + options: ["stroustrup"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "blockSameLine", type: "Punctuator" } + ] + }, + { + code: unIndent` + class C { + static { + foo;} + } + `, + output: unIndent` + class C { + static { + foo; + } + } + `, + options: ["stroustrup"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "singleLineClose", type: "Punctuator" } + ] + }, + { + code: unIndent` + class C { + static + {foo;} + } + `, + output: unIndent` + class C { + static { + foo; + } + } + `, + options: ["stroustrup"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "nextLineOpen", type: "Punctuator" }, + { messageId: "blockSameLine", type: "Punctuator" }, + { messageId: "singleLineClose", type: "Punctuator" } + ] + }, + { + code: unIndent` + class C { + static + {} + } + `, + output: unIndent` + class C { + static {} + } + `, + options: ["stroustrup"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "nextLineOpen", type: "Punctuator" } + ] + }, + { + code: unIndent` + class C + { + static{ + foo; + } + } + `, + output: unIndent` + class C + { + static + { + foo; + } + } + `, + options: ["allman"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "sameLineOpen", type: "Punctuator" } + ] + }, + { + code: unIndent` + class C + { + static + {foo; + } + } + `, + output: unIndent` + class C + { + static + { + foo; + } + } + `, + options: ["allman"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "blockSameLine", type: "Punctuator" } + ] + }, + { + code: unIndent` + class C + { + static + { + foo;} + } + `, + output: unIndent` + class C + { + static + { + foo; + } + } + `, + options: ["allman"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "singleLineClose", type: "Punctuator" } + ] + }, + { + code: unIndent` + class C + { + static{foo;} + } + `, + output: unIndent` + class C + { + static + { + foo; + } + } + `, + options: ["allman"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "sameLineOpen", type: "Punctuator" }, + { messageId: "blockSameLine", type: "Punctuator" }, + { messageId: "singleLineClose", type: "Punctuator" } + ] + }, + { + code: unIndent` + class C + { + static{} + } + `, + output: unIndent` + class C + { + static + {} + } + `, + options: ["allman"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "sameLineOpen", type: "Punctuator" } + ] } ] }); diff --git a/eslint/tests/lib/rules/camelcase.js b/eslint/tests/lib/rules/camelcase.js index 4f9cdca..514eb85 100644 --- a/eslint/tests/lib/rules/camelcase.js +++ b/eslint/tests/lib/rules/camelcase.js @@ -182,90 +182,90 @@ ruleTester.run("camelcase", rule, { { code: "var camelCased = a_global_variable", options: [{ ignoreGlobals: true }], - globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase + globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase -- Testing non-CamelCase }, { code: "a_global_variable.foo()", options: [{ ignoreGlobals: true }], - globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase + globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase -- Testing non-CamelCase }, { code: "a_global_variable[undefined]", options: [{ ignoreGlobals: true }], - globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase + globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase -- Testing non-CamelCase }, { code: "var foo = a_global_variable.bar", options: [{ ignoreGlobals: true }], - globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase + globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase -- Testing non-CamelCase }, { code: "a_global_variable.foo = bar", options: [{ ignoreGlobals: true }], - globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase + globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase -- Testing non-CamelCase }, { code: "( { foo: a_global_variable.bar } = baz )", options: [{ ignoreGlobals: true }], parserOptions: { ecmaVersion: 6 }, - globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase + globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase -- Testing non-CamelCase }, { code: "a_global_variable = foo", options: [{ ignoreGlobals: true }], - globals: { a_global_variable: "writable" } // eslint-disable-line camelcase + globals: { a_global_variable: "writable" } // eslint-disable-line camelcase -- Testing non-CamelCase }, { code: "a_global_variable = foo", options: [{ ignoreGlobals: true }], - globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase + globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase -- Testing non-CamelCase }, { code: "({ a_global_variable } = foo)", options: [{ ignoreGlobals: true }], parserOptions: { ecmaVersion: 6 }, - globals: { a_global_variable: "writable" } // eslint-disable-line camelcase + globals: { a_global_variable: "writable" } // eslint-disable-line camelcase -- Testing non-CamelCase }, { code: "({ snake_cased: a_global_variable } = foo)", options: [{ ignoreGlobals: true }], parserOptions: { ecmaVersion: 6 }, - globals: { a_global_variable: "writable" } // eslint-disable-line camelcase + globals: { a_global_variable: "writable" } // eslint-disable-line camelcase -- Testing non-CamelCase }, { code: "({ snake_cased: a_global_variable = foo } = bar)", options: [{ ignoreGlobals: true }], parserOptions: { ecmaVersion: 6 }, - globals: { a_global_variable: "writable" } // eslint-disable-line camelcase + globals: { a_global_variable: "writable" } // eslint-disable-line camelcase -- Testing non-CamelCase }, { code: "[a_global_variable] = bar", options: [{ ignoreGlobals: true }], parserOptions: { ecmaVersion: 6 }, - globals: { a_global_variable: "writable" } // eslint-disable-line camelcase + globals: { a_global_variable: "writable" } // eslint-disable-line camelcase -- Testing non-CamelCase }, { code: "[a_global_variable = foo] = bar", options: [{ ignoreGlobals: true }], parserOptions: { ecmaVersion: 6 }, - globals: { a_global_variable: "writable" } // eslint-disable-line camelcase + globals: { a_global_variable: "writable" } // eslint-disable-line camelcase -- Testing non-CamelCase }, { code: "foo[a_global_variable] = bar", options: [{ ignoreGlobals: true }], - globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase + globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase -- Testing non-CamelCase }, { code: "var foo = { [a_global_variable]: bar }", options: [{ ignoreGlobals: true }], parserOptions: { ecmaVersion: 6 }, - globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase + globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase -- Testing non-CamelCase }, { code: "var { [a_global_variable]: foo } = bar", options: [{ ignoreGlobals: true }], parserOptions: { ecmaVersion: 6 }, - globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase + globals: { a_global_variable: "readonly" } // eslint-disable-line camelcase -- Testing non-CamelCase }, { code: "function foo({ no_camelcased: camelCased }) {};", @@ -376,6 +376,16 @@ ruleTester.run("camelcase", rule, { code: "([obj.foo = obj.fo_o] = bar);", options: [{ properties: "always" }], parserOptions: { ecmaVersion: 6 } + }, + { + code: "class C { camelCase; #camelCase; #camelCase2() {} }", + options: [{ properties: "always" }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { snake_case; #snake_case; #snake_case2() {} }", + options: [{ properties: "never" }], + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -753,7 +763,7 @@ ruleTester.run("camelcase", rule, { { code: "var camelCased = snake_cased", options: [{ ignoreGlobals: false }], - globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase + globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase -- Testing non-CamelCase errors: [ { messageId: "notCamelCase", @@ -765,7 +775,7 @@ ruleTester.run("camelcase", rule, { { code: "a_global_variable.foo()", options: [{ ignoreGlobals: false }], - globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase + globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase -- Testing non-CamelCase errors: [ { messageId: "notCamelCase", @@ -777,7 +787,7 @@ ruleTester.run("camelcase", rule, { { code: "a_global_variable[undefined]", options: [{ ignoreGlobals: false }], - globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase + globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase -- Testing non-CamelCase errors: [ { messageId: "notCamelCase", @@ -788,7 +798,7 @@ ruleTester.run("camelcase", rule, { }, { code: "var camelCased = snake_cased", - globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase + globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase -- Testing non-CamelCase errors: [ { messageId: "notCamelCase", @@ -800,7 +810,7 @@ ruleTester.run("camelcase", rule, { { code: "var camelCased = snake_cased", options: [{}], - globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase + globals: { snake_cased: "readonly" }, // eslint-disable-line camelcase -- Testing non-CamelCase errors: [ { messageId: "notCamelCase", @@ -812,7 +822,7 @@ ruleTester.run("camelcase", rule, { { code: "foo.a_global_variable = bar", options: [{ ignoreGlobals: true }], - globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase + globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase -- Testing non-CamelCase errors: [ { messageId: "notCamelCase", @@ -824,7 +834,7 @@ ruleTester.run("camelcase", rule, { { code: "var foo = { a_global_variable: bar }", options: [{ ignoreGlobals: true }], - globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase + globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase -- Testing non-CamelCase errors: [ { messageId: "notCamelCase", @@ -836,7 +846,7 @@ ruleTester.run("camelcase", rule, { { code: "var foo = { a_global_variable: a_global_variable }", options: [{ ignoreGlobals: true }], - globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase + globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase -- Testing non-CamelCase errors: [ { messageId: "notCamelCase", @@ -850,7 +860,7 @@ ruleTester.run("camelcase", rule, { code: "var foo = { a_global_variable() {} }", options: [{ ignoreGlobals: true }], parserOptions: { ecmaVersion: 6 }, - globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase + globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase -- Testing non-CamelCase errors: [ { messageId: "notCamelCase", @@ -863,7 +873,7 @@ ruleTester.run("camelcase", rule, { code: "class Foo { a_global_variable() {} }", options: [{ ignoreGlobals: true }], parserOptions: { ecmaVersion: 6 }, - globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase + globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase -- Testing non-CamelCase errors: [ { messageId: "notCamelCase", @@ -875,7 +885,7 @@ ruleTester.run("camelcase", rule, { { code: "a_global_variable: for (;;);", options: [{ ignoreGlobals: true }], - globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase + globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase -- Testing non-CamelCase errors: [ { messageId: "notCamelCase", @@ -888,7 +898,7 @@ ruleTester.run("camelcase", rule, { code: "if (foo) { let a_global_variable; a_global_variable = bar; }", options: [{ ignoreGlobals: true }], parserOptions: { ecmaVersion: 6 }, - globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase + globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase -- Testing non-CamelCase errors: [ { messageId: "notCamelCase", @@ -908,7 +918,7 @@ ruleTester.run("camelcase", rule, { code: "function foo(a_global_variable) { foo = a_global_variable; }", options: [{ ignoreGlobals: true }], parserOptions: { ecmaVersion: 6 }, - globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase + globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase -- Testing non-CamelCase errors: [ { messageId: "notCamelCase", @@ -952,7 +962,7 @@ ruleTester.run("camelcase", rule, { code: "const a_global_variable = foo; bar = a_global_variable", options: [{ ignoreGlobals: true }], parserOptions: { ecmaVersion: 6 }, - globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase + globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase -- Testing non-CamelCase errors: [ { messageId: "notCamelCase", @@ -972,7 +982,7 @@ ruleTester.run("camelcase", rule, { code: "bar = a_global_variable; var a_global_variable;", options: [{ ignoreGlobals: true }], parserOptions: { ecmaVersion: 6 }, - globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase + globals: { a_global_variable: "writable" }, // eslint-disable-line camelcase -- Testing non-CamelCase errors: [ { messageId: "notCamelCase", @@ -992,7 +1002,7 @@ ruleTester.run("camelcase", rule, { code: "var foo = { a_global_variable }", options: [{ ignoreGlobals: true }], parserOptions: { ecmaVersion: 6 }, - globals: { a_global_variable: "readonly" }, // eslint-disable-line camelcase + globals: { a_global_variable: "readonly" }, // eslint-disable-line camelcase -- Testing non-CamelCase errors: [ { messageId: "notCamelCase", @@ -1001,6 +1011,28 @@ ruleTester.run("camelcase", rule, { } ] }, + { + code: "undefined_variable;", + options: [{ ignoreGlobals: true }], + errors: [ + { + messageId: "notCamelCase", + data: { name: "undefined_variable" }, + type: "Identifier" + } + ] + }, + { + code: "implicit_global = 1;", + options: [{ ignoreGlobals: true }], + errors: [ + { + messageId: "notCamelCase", + data: { name: "implicit_global" }, + type: "Identifier" + } + ] + }, { code: "export * as snake_cased from 'mod'", parserOptions: { ecmaVersion: 2020, sourceType: "module" }, @@ -1320,6 +1352,26 @@ ruleTester.run("camelcase", rule, { options: [{ properties: "always" }], parserOptions: { ecmaVersion: 2020 }, errors: [{ messageId: "notCamelCase", data: { name: "non_camelcase" } }] + }, + + // class public/private fields, private methods. + { + code: "class C { snake_case; }", + options: [{ properties: "always" }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "notCamelCase", data: { name: "snake_case" } }] + }, + { + code: "class C { #snake_case; foo() { this.#snake_case; } }", + options: [{ properties: "always" }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "notCamelCasePrivate", data: { name: "snake_case" }, column: 11 }] + }, + { + code: "class C { #snake_case() {} }", + options: [{ properties: "always" }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "notCamelCasePrivate", data: { name: "snake_case" } }] } ] }); diff --git a/eslint/tests/lib/rules/class-methods-use-this.js b/eslint/tests/lib/rules/class-methods-use-this.js index e307d6a..eeb122f 100644 --- a/eslint/tests/lib/rules/class-methods-use-this.js +++ b/eslint/tests/lib/rules/class-methods-use-this.js @@ -30,56 +30,68 @@ ruleTester.run("class-methods-use-this", rule, { { code: "({ a(){} });", parserOptions: { ecmaVersion: 6 } }, { code: "class A { foo() { () => this; } }", parserOptions: { ecmaVersion: 6 } }, { code: "({ a: function () {} });", parserOptions: { ecmaVersion: 6 } }, - { code: "class A { foo() {this} bar() {} }", options: [{ exceptMethods: ["bar"] }], parserOptions: { ecmaVersion: 6 } } + { code: "class A { foo() {this} bar() {} }", options: [{ exceptMethods: ["bar"] }], parserOptions: { ecmaVersion: 6 } }, + { code: "class A { \"foo\"() { } }", options: [{ exceptMethods: ["foo"] }], parserOptions: { ecmaVersion: 6 } }, + { code: "class A { 42() { } }", options: [{ exceptMethods: ["42"] }], parserOptions: { ecmaVersion: 6 } }, + { code: "class A { foo = function() {this} }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { foo = () => {this} }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { foo = () => {super.toString} }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { static foo = function() {} }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { static foo = () => {} }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { #bar() {} }", options: [{ exceptMethods: ["#bar"] }], parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { foo = function () {} }", options: [{ enforceForClassFields: false }], parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { foo = () => {} }", options: [{ enforceForClassFields: false }], parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { foo() { return class { [this.foo] = 1 }; } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { static {} }", parserOptions: { ecmaVersion: 2022 } } ], invalid: [ { code: "class A { foo() {} }", parserOptions: { ecmaVersion: 6 }, errors: [ - { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } } + { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } } ] }, { code: "class A { foo() {/**this**/} }", parserOptions: { ecmaVersion: 6 }, errors: [ - { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } } + { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } } ] }, { code: "class A { foo() {var a = function () {this};} }", parserOptions: { ecmaVersion: 6 }, errors: [ - { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } } + { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } } ] }, { code: "class A { foo() {var a = function () {var b = function(){this}};} }", parserOptions: { ecmaVersion: 6 }, errors: [ - { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } } + { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } } ] }, { code: "class A { foo() {window.this} }", parserOptions: { ecmaVersion: 6 }, errors: [ - { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } } + { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } } ] }, { code: "class A { foo() {that.this = 'this';} }", parserOptions: { ecmaVersion: 6 }, errors: [ - { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } } + { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } } ] }, { code: "class A { foo() { () => undefined; } }", parserOptions: { ecmaVersion: 6 }, errors: [ - { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } } + { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } } ] }, { @@ -87,7 +99,7 @@ ruleTester.run("class-methods-use-this", rule, { options: [{ exceptMethods: ["bar"] }], parserOptions: { ecmaVersion: 6 }, errors: [ - { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } } + { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } } ] }, { @@ -95,7 +107,7 @@ ruleTester.run("class-methods-use-this", rule, { options: [{ exceptMethods: ["foo"] }], parserOptions: { ecmaVersion: 6 }, errors: [ - { type: "FunctionExpression", line: 1, column: 34, messageId: "missingThis", data: { name: "method 'hasOwnProperty'" } } + { type: "FunctionExpression", line: 1, column: 20, messageId: "missingThis", data: { name: "method 'hasOwnProperty'" } } ] }, { @@ -103,22 +115,101 @@ ruleTester.run("class-methods-use-this", rule, { options: [{ exceptMethods: ["foo"] }], parserOptions: { ecmaVersion: 6 }, errors: [ - { type: "FunctionExpression", line: 1, column: 16, messageId: "missingThis", data: { name: "method" } } + { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method" } } + ] + }, + { + code: "class A { #foo() { } foo() {} #bar() {} }", + options: [{ exceptMethods: ["#foo"] }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { type: "FunctionExpression", line: 1, column: 22, messageId: "missingThis", data: { name: "method 'foo'" } }, + { type: "FunctionExpression", line: 1, column: 31, messageId: "missingThis", data: { name: "private method #bar" } } ] }, { code: "class A { foo(){} 'bar'(){} 123(){} [`baz`](){} [a](){} [f(a)](){} get quux(){} set[a](b){} *quuux(){} }", parserOptions: { ecmaVersion: 6 }, errors: [ - { messageId: "missingThis", data: { name: "method 'foo'" }, type: "FunctionExpression", column: 14 }, - { messageId: "missingThis", data: { name: "method 'bar'" }, type: "FunctionExpression", column: 24 }, - { messageId: "missingThis", data: { name: "method '123'" }, type: "FunctionExpression", column: 32 }, - { messageId: "missingThis", data: { name: "method 'baz'" }, type: "FunctionExpression", column: 44 }, - { messageId: "missingThis", data: { name: "method" }, type: "FunctionExpression", column: 52 }, - { messageId: "missingThis", data: { name: "method" }, type: "FunctionExpression", column: 63 }, - { messageId: "missingThis", data: { name: "getter 'quux'" }, type: "FunctionExpression", column: 76 }, - { messageId: "missingThis", data: { name: "setter" }, type: "FunctionExpression", column: 87 }, - { messageId: "missingThis", data: { name: "generator method 'quuux'" }, type: "FunctionExpression", column: 99 } + { messageId: "missingThis", data: { name: "method 'foo'" }, type: "FunctionExpression", column: 11 }, + { messageId: "missingThis", data: { name: "method 'bar'" }, type: "FunctionExpression", column: 19 }, + { messageId: "missingThis", data: { name: "method '123'" }, type: "FunctionExpression", column: 29 }, + { messageId: "missingThis", data: { name: "method 'baz'" }, type: "FunctionExpression", column: 37 }, + { messageId: "missingThis", data: { name: "method" }, type: "FunctionExpression", column: 49 }, + { messageId: "missingThis", data: { name: "method" }, type: "FunctionExpression", column: 57 }, + { messageId: "missingThis", data: { name: "getter 'quux'" }, type: "FunctionExpression", column: 68 }, + { messageId: "missingThis", data: { name: "setter" }, type: "FunctionExpression", column: 81 }, + { messageId: "missingThis", data: { name: "generator method 'quuux'" }, type: "FunctionExpression", column: 93 } + ] + }, + { + code: "class A { foo = function() {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "missingThis", data: { name: "method 'foo'" }, column: 11, endColumn: 25 } + ] + }, + { + code: "class A { foo = () => {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "missingThis", data: { name: "method 'foo'" }, column: 11, endColumn: 17 } + ] + }, + { + code: "class A { #foo = function() {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "missingThis", data: { name: "private method #foo" }, column: 11, endColumn: 26 } + ] + }, + { + code: "class A { #foo = () => {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "missingThis", data: { name: "private method #foo" }, column: 11, endColumn: 18 } + ] + }, + { + code: "class A { #foo() {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "missingThis", data: { name: "private method #foo" }, column: 11, endColumn: 15 } + ] + }, + { + code: "class A { get #foo() {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "missingThis", data: { name: "private getter #foo" }, column: 11, endColumn: 19 } + ] + }, + { + code: "class A { set #foo(x) {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "missingThis", data: { name: "private setter #foo" }, column: 11, endColumn: 19 } + ] + }, + { + code: "class A { foo () { return class { foo = this }; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "missingThis", data: { name: "method 'foo'" }, column: 11, endColumn: 15 } + ] + }, + { + code: "class A { foo () { return function () { foo = this }; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "missingThis", data: { name: "method 'foo'" }, column: 11, endColumn: 15 } + ] + }, + { + code: "class A { foo () { return class { static { this; } } } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "missingThis", data: { name: "method 'foo'" }, column: 11, endColumn: 15 } ] } ] diff --git a/eslint/tests/lib/rules/comma-style.js b/eslint/tests/lib/rules/comma-style.js index 1eb3082..e0ac78d 100644 --- a/eslint/tests/lib/rules/comma-style.js +++ b/eslint/tests/lib/rules/comma-style.js @@ -233,7 +233,45 @@ ruleTester.run("comma-style", rule, { NewExpression: true } }] + }, + "var foo = [\n , \n 1, \n 2 \n];", + { + code: "const [\n , \n , \n a, \n b, \n] = arr;", + options: ["last", { + exceptions: { + ArrayPattern: false + } + }], + parserOptions: { + ecmaVersion: 6 + } + }, + { + code: "const [\n ,, \n a, \n b, \n] = arr;", + options: ["last", { + exceptions: { + ArrayPattern: false + } + }], + parserOptions: { + ecmaVersion: 6 + } + }, + { + code: "const arr = [\n 1 \n , \n ,2 \n]", + options: ["first"], + parserOptions: { + ecmaVersion: 6 + } + }, + { + code: "const arr = [\n ,'fifi' \n]", + options: ["first"], + parserOptions: { + ecmaVersion: 6 + } } + ], invalid: [ diff --git a/eslint/tests/lib/rules/complexity.js b/eslint/tests/lib/rules/complexity.js index cf7605b..d6feb8a 100644 --- a/eslint/tests/lib/rules/complexity.js +++ b/eslint/tests/lib/rules/complexity.js @@ -36,10 +36,10 @@ function createComplexity(complexity) { /** * Create an expected error object - * @param {string} name The name of the symbol being tested - * @param {number} complexity The cyclomatic complexity value of the symbol - * @param {number} max The maximum cyclomatic complexity value of the symbol - * @returns {Object} The error object + * @param {string} name The name of the symbol being tested + * @param {number} complexity The cyclomatic complexity value of the symbol + * @param {number} max The maximum cyclomatic complexity value of the symbol + * @returns {Object} The error object */ function makeError(name, complexity, max) { return { @@ -48,6 +48,10 @@ function makeError(name, complexity, max) { }; } +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2021 } }); ruleTester.run("complexity", rule, { @@ -85,6 +89,40 @@ ruleTester.run("complexity", rule, { { code: "if (foo) { bar(); }", options: [3] }, { code: "var a = (x) => {do {'foo';} while (true)}", options: [2], parserOptions: { ecmaVersion: 6 } }, + // class fields + { code: "function foo() { class C { x = a || b; y = c || d; } }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "function foo() { class C { static x = a || b; static y = c || d; } }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "function foo() { class C { x = a || b; y = c || d; } e || f; }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "function foo() { a || b; class C { x = c || d; y = e || f; } }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "function foo() { class C { [x || y] = a || b; } }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { x = a || b; y() { c || d; } z = e || f; }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { x() { a || b; } y = c || d; z() { e || f; } }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { x = (() => { a || b }) || (() => { c || d }) }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { x = () => { a || b }; y = () => { c || d } }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { x = a || (() => { b || c }); }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { x = class { y = a || b; z = c || d; }; }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { x = a || class { y = b || c; z = d || e; }; }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { x; y = a; static z; static q = b; }", options: [1], parserOptions: { ecmaVersion: 2022 } }, + + // class static blocks + { code: "function foo() { class C { static { a || b; } static { c || d; } } }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "function foo() { a || b; class C { static { c || d; } } }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "function foo() { class C { static { a || b; } } c || d; }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "function foo() { class C { static { a || b; } } class D { static { c || d; } } }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { a || b; } static { c || d; } }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { a || b; } static { c || d; } static { e || f; } }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { () => a || b; c || d; } }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { a || b; () => c || d; } static { c || d; } }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { a } }", options: [1], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { a } static { b } }", options: [1], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { a || b; } } class D { static { c || d; } }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { a || b; } static c = d || e; }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static a = b || c; static { c || d; } }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { a || b; } c = d || e; }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { a = b || c; static { d || e; } }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { a || b; c || d; } }", options: [3], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { if (a || b) c = d || e; } }", options: [4], parserOptions: { ecmaVersion: 2022 } }, + // object property options { code: "function b(x) {}", options: [{ max: 1 }] } ], @@ -118,7 +156,7 @@ ruleTester.run("complexity", rule, { { code: "function a(x) {(function() {while(true){'foo';}})(); (function() {while(true){'bar';}})();}", options: [1], errors: 2 }, { code: "function a(x) {(function() {while(true){'foo';}})(); (function() {'bar';})();}", options: [1], errors: 1 }, { code: "var obj = { a(x) { return x ? 0 : 1; } };", options: [1], parserOptions: { ecmaVersion: 6 }, errors: [makeError("Method 'a'", 2, 1)] }, - { code: "var obj = { a: function b(x) { return x ? 0 : 1; } };", options: [1], errors: [makeError("Method 'b'", 2, 1)] }, + { code: "var obj = { a: function b(x) { return x ? 0 : 1; } };", options: [1], errors: [makeError("Method 'a'", 2, 1)] }, { code: createComplexity(21), errors: [makeError("Function 'test'", 21, 20)] @@ -129,6 +167,360 @@ ruleTester.run("complexity", rule, { errors: [makeError("Function 'test'", 21, 20)] }, + // class fields + { + code: "function foo () { a || b; class C { x; } c || d; }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Function 'foo'", 3, 2)] + }, + { + code: "function foo () { a || b; class C { x = c; } d || e; }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Function 'foo'", 3, 2)] + }, + { + code: "function foo () { a || b; class C { [x || y]; } }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Function 'foo'", 3, 2)] + }, + { + code: "function foo () { a || b; class C { [x || y] = c; } }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Function 'foo'", 3, 2)] + }, + { + code: "function foo () { class C { [x || y]; } a || b; }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Function 'foo'", 3, 2)] + }, + { + code: "function foo () { class C { [x || y] = a; } b || c; }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Function 'foo'", 3, 2)] + }, + { + code: "function foo () { class C { [x || y]; [z || q]; } }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Function 'foo'", 3, 2)] + }, + { + code: "function foo () { class C { [x || y] = a; [z || q] = b; } }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Function 'foo'", 3, 2)] + }, + { + code: "function foo () { a || b; class C { x = c || d; } e || f; }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Function 'foo'", 3, 2)] + }, + { + code: "class C { x(){ a || b; } y = c || d || e; z() { f || g; } }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class field initializer", 3, 2)] + }, + { + code: "class C { x = a || b; y() { c || d || e; } z = f || g; }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Method 'y'", 3, 2)] + }, + { + code: "class C { x; y() { c || d || e; } z; }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Method 'y'", 3, 2)] + }, + { + code: "class C { x = a || b; }", + options: [1], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class field initializer", 2, 1)] + }, + { + code: "(class { x = a || b; })", + options: [1], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class field initializer", 2, 1)] + }, + { + code: "class C { static x = a || b; }", + options: [1], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class field initializer", 2, 1)] + }, + { + code: "(class { x = a ? b : c; })", + options: [1], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class field initializer", 2, 1)] + }, + { + code: "class C { x = a || b || c; }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class field initializer", 3, 2)] + }, + { + code: "class C { x = a || b; y = b || c || d; z = e || f; }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + ...makeError("Class field initializer", 3, 2), + line: 1, + column: 27, + endLine: 1, + endColumn: 38 + }] + }, + { + code: "class C { x = a || b || c; y = d || e; z = f || g || h; }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + ...makeError("Class field initializer", 3, 2), + line: 1, + column: 15, + endLine: 1, + endColumn: 26 + }, + { + ...makeError("Class field initializer", 3, 2), + line: 1, + column: 44, + endLine: 1, + endColumn: 55 + } + ] + }, + { + code: "class C { x = () => a || b || c; }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Method 'x'", 3, 2)] + }, + { + code: "class C { x = (() => a || b || c) || d; }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Arrow function", 3, 2)] + }, + { + code: "class C { x = () => a || b || c; y = d || e; }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Method 'x'", 3, 2)] + }, + { + code: "class C { x = () => a || b || c; y = d || e || f; }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + makeError("Method 'x'", 3, 2), + { + ...makeError("Class field initializer", 3, 2), + line: 1, + column: 38, + endLine: 1, + endColumn: 49 + } + ] + }, + { + code: "class C { x = function () { a || b }; y = function () { c || d }; }", + options: [1], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + makeError("Method 'x'", 2, 1), + makeError("Method 'y'", 2, 1) + ] + }, + { + code: "class C { x = class { [y || z]; }; }", + options: [1], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + ...makeError("Class field initializer", 2, 1), + line: 1, + column: 15, + endLine: 1, + endColumn: 34 + } + ] + }, + { + code: "class C { x = class { [y || z] = a; }; }", + options: [1], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + ...makeError("Class field initializer", 2, 1), + line: 1, + column: 15, + endLine: 1, + endColumn: 38 + } + ] + }, + { + code: "class C { x = class { y = a || b; }; }", + options: [1], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + ...makeError("Class field initializer", 2, 1), + line: 1, + column: 27, + endLine: 1, + endColumn: 33 + } + ] + }, + + // class static blocks + { + code: "function foo () { a || b; class C { static {} } c || d; }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Function 'foo'", 3, 2)] + }, + { + code: "function foo () { a || b; class C { static { c || d; } } e || f; }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Function 'foo'", 3, 2)] + }, + { + code: "class C { static { a || b; } }", + options: [1], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class static block", 2, 1)] + }, + { + code: "class C { static { a || b || c; } }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class static block", 3, 2)] + }, + { + code: "class C { static { a || b; c || d; } }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class static block", 3, 2)] + }, + { + code: "class C { static { a || b; c || d; e || f; } }", + options: [3], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class static block", 4, 3)] + }, + { + code: "class C { static { a || b; c || d; { e || f; } } }", + options: [3], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class static block", 4, 3)] + }, + { + code: "class C { static { if (a || b) c = d || e; } }", + options: [3], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class static block", 4, 3)] + }, + { + code: "class C { static { if (a || b) c = (d => e || f)() || (g => h || i)(); } }", + options: [3], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class static block", 4, 3)] + }, + { + code: "class C { x(){ a || b; } static { c || d || e; } z() { f || g; } }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class static block", 3, 2)] + }, + { + code: "class C { x = a || b; static { c || d || e; } y = f || g; }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class static block", 3, 2)] + }, + { + code: "class C { static x = a || b; static { c || d || e; } static y = f || g; }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class static block", 3, 2)] + }, + { + code: "class C { static { a || b; } static(){ c || d || e; } static { f || g; } }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Method 'static'", 3, 2)] + }, + { + code: "class C { static { a || b; } static static(){ c || d || e; } static { f || g; } }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [makeError("Static method 'static'", 3, 2)] + }, + { + code: "class C { static { a || b; } static x = c || d || e; static { f || g; } }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + ...makeError("Class field initializer", 3, 2), + column: 41, + endColumn: 52 + }] + }, + { + code: "class C { static { a || b || c || d; } static { e || f || g; } }", + options: [3], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + ...makeError("Class static block", 4, 3), + column: 11, + endColumn: 39 + }] + }, + { + code: "class C { static { a || b || c; } static { d || e || f || g; } }", + options: [3], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + ...makeError("Class static block", 4, 3), + column: 35, + endColumn: 63 + }] + }, + { + code: "class C { static { a || b || c || d; } static { e || f || g || h; } }", + options: [3], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + ...makeError("Class static block", 4, 3), + column: 11, + endColumn: 39 + }, + { + ...makeError("Class static block", 4, 3), + column: 40, + endColumn: 68 + } + ] + }, + // object property options { code: "function a(x) {}", options: [{ max: 0 }], errors: [makeError("Function 'a'", 1, 0)] } ] diff --git a/eslint/tests/lib/rules/computed-property-spacing.js b/eslint/tests/lib/rules/computed-property-spacing.js index a1e5834..eff0c0a 100644 --- a/eslint/tests/lib/rules/computed-property-spacing.js +++ b/eslint/tests/lib/rules/computed-property-spacing.js @@ -99,6 +99,16 @@ ruleTester.run("computed-property-spacing", rule, { options: ["always", { enforceForClassMembers: false }], parserOptions: { ecmaVersion: 6 } }, + { + code: "class A { [ a ]; }", + options: ["never", { enforceForClassMembers: false }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class A { [a]; }", + options: ["always", { enforceForClassMembers: false }], + parserOptions: { ecmaVersion: 2022 } + }, // valid spacing { @@ -141,6 +151,16 @@ ruleTester.run("computed-property-spacing", rule, { options: ["always", { enforceForClassMembers: true }], parserOptions: { ecmaVersion: 6 } }, + { + code: "A = class { [a]; static [a]; [a] = 0; static [a] = 0; }", + options: ["never", { enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "A = class { [ a ]; static [ a ]; [ a ] = 0; static [ a ] = 0; }", + options: ["always", { enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 2022 } + }, // non-computed { @@ -153,6 +173,16 @@ ruleTester.run("computed-property-spacing", rule, { options: ["always", { enforceForClassMembers: true }], parserOptions: { ecmaVersion: 6 } }, + { + code: "A = class { foo; #a; static #b; #c = 0; static #d = 0; }", + options: ["never", { enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "A = class { foo; #a; static #b; #c = 0; static #d = 0; }", + options: ["always", { enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 2022 } + }, // handling of parens and comments { @@ -1349,6 +1379,62 @@ ruleTester.run("computed-property-spacing", rule, { } ] }, + { + code: "class A { [ a]; [b ]; [ c ]; [ a] = 0; [b ] = 0; [ c ] = 0; }", + output: "class A { [a]; [b]; [c]; [a] = 0; [b] = 0; [c] = 0; }", + options: ["never", { enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + column: 12, + endColumn: 13 + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + column: 19, + endColumn: 20 + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + column: 24, + endColumn: 25 + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + column: 26, + endColumn: 27 + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + column: 31, + endColumn: 32 + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + column: 42, + endColumn: 43 + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + column: 51, + endColumn: 52 + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + column: 53, + endColumn: 54 + } + ] + }, // always - classes { @@ -1545,6 +1631,54 @@ ruleTester.run("computed-property-spacing", rule, { } ] }, + { + code: "class A { [ a]; [b ]; [c]; [ a] = 0; [b ] = 0; [c] = 0; }", + output: "class A { [ a ]; [ b ]; [ c ]; [ a ] = 0; [ b ] = 0; [ c ] = 0; }", + options: ["always", { enforceForClassMembers: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "missingSpaceBefore", + column: 14, + endColumn: 15 + }, + { + messageId: "missingSpaceAfter", + column: 17, + endColumn: 18 + }, + { + messageId: "missingSpaceAfter", + column: 23, + endColumn: 24 + }, + { + messageId: "missingSpaceBefore", + column: 25, + endColumn: 26 + }, + { + messageId: "missingSpaceBefore", + column: 31, + endColumn: 32 + }, + { + messageId: "missingSpaceAfter", + column: 38, + endColumn: 39 + }, + { + messageId: "missingSpaceAfter", + column: 48, + endColumn: 49 + }, + { + messageId: "missingSpaceBefore", + column: 50, + endColumn: 51 + } + ] + }, // handling of parens and comments { diff --git a/eslint/tests/lib/rules/consistent-return.js b/eslint/tests/lib/rules/consistent-return.js index 53781ea..16a1ed9 100644 --- a/eslint/tests/lib/rules/consistent-return.js +++ b/eslint/tests/lib/rules/consistent-return.js @@ -52,7 +52,11 @@ ruleTester.run("consistent-return", rule, { { messageId: "missingReturnValue", data: { name: "Function 'foo'" }, - type: "ReturnStatement" + type: "ReturnStatement", + line: 1, + column: 46, + endLine: 1, + endColumn: 53 } ] }, @@ -63,7 +67,11 @@ ruleTester.run("consistent-return", rule, { { messageId: "missingReturnValue", data: { name: "Arrow function" }, - type: "ReturnStatement" + type: "ReturnStatement", + line: 1, + column: 47, + endLine: 1, + endColumn: 54 } ] }, @@ -73,7 +81,11 @@ ruleTester.run("consistent-return", rule, { { messageId: "unexpectedReturnValue", data: { name: "Function 'foo'" }, - type: "ReturnStatement" + type: "ReturnStatement", + line: 1, + column: 41, + endLine: 1, + endColumn: 54 } ] }, @@ -83,7 +95,11 @@ ruleTester.run("consistent-return", rule, { { messageId: "missingReturnValue", data: { name: "Function" }, - type: "ReturnStatement" + type: "ReturnStatement", + line: 1, + column: 44, + endLine: 1, + endColumn: 51 } ] }, @@ -93,7 +109,11 @@ ruleTester.run("consistent-return", rule, { { messageId: "unexpectedReturnValue", data: { name: "Function" }, - type: "ReturnStatement" + type: "ReturnStatement", + line: 1, + column: 39, + endLine: 1, + endColumn: 52 } ] }, @@ -104,7 +124,11 @@ ruleTester.run("consistent-return", rule, { { messageId: "unexpectedReturnValue", data: { name: "Arrow function" }, - type: "ReturnStatement" + type: "ReturnStatement", + line: 1, + column: 33, + endLine: 1, + endColumn: 46 } ] }, @@ -116,7 +140,10 @@ ruleTester.run("consistent-return", rule, { messageId: "missingReturnValue", data: { name: "Function 'foo'" }, type: "ReturnStatement", - column: 41 + line: 1, + column: 41, + endLine: 1, + endColumn: 58 } ] }, @@ -128,7 +155,10 @@ ruleTester.run("consistent-return", rule, { messageId: "missingReturnValue", data: { name: "Function 'foo'" }, type: "ReturnStatement", - column: 41 + line: 1, + column: 41, + endLine: 1, + endColumn: 55 } ] }, @@ -140,7 +170,10 @@ ruleTester.run("consistent-return", rule, { messageId: "unexpectedReturnValue", data: { name: "Function 'foo'" }, type: "ReturnStatement", - column: 46 + line: 1, + column: 46, + endLine: 1, + endColumn: 58 } ] }, @@ -152,7 +185,10 @@ ruleTester.run("consistent-return", rule, { messageId: "unexpectedReturnValue", data: { name: "Function 'foo'" }, type: "ReturnStatement", - column: 43 + line: 1, + column: 43, + endLine: 1, + endColumn: 55 } ] }, @@ -163,7 +199,11 @@ ruleTester.run("consistent-return", rule, { { messageId: "missingReturnValue", data: { name: "Program" }, - type: "ReturnStatement" + type: "ReturnStatement", + line: 1, + column: 25, + endLine: 1, + endColumn: 32 } ] }, @@ -174,7 +214,10 @@ ruleTester.run("consistent-return", rule, { messageId: "missingReturn", data: { name: "function 'foo'" }, type: "FunctionDeclaration", - column: 10 + line: 1, + column: 10, + endLine: 1, + endColumn: 13 } ] }, @@ -185,7 +228,10 @@ ruleTester.run("consistent-return", rule, { messageId: "missingReturn", data: { name: "function '_foo'" }, type: "FunctionDeclaration", - column: 10 + line: 1, + column: 10, + endLine: 1, + endColumn: 14 } ] }, @@ -196,7 +242,10 @@ ruleTester.run("consistent-return", rule, { messageId: "missingReturn", data: { name: "function 'foo'" }, type: "FunctionExpression", - column: 12 + line: 1, + column: 12, + endLine: 1, + endColumn: 15 } ] }, @@ -207,7 +256,10 @@ ruleTester.run("consistent-return", rule, { messageId: "missingReturn", data: { name: "function" }, type: "FunctionExpression", - column: 3 + line: 1, + column: 3, + endLine: 1, + endColumn: 11 } ] }, @@ -219,7 +271,10 @@ ruleTester.run("consistent-return", rule, { messageId: "missingReturn", data: { name: "arrow function" }, type: "ArrowFunctionExpression", - column: 6 + line: 1, + column: 6, + endLine: 1, + endColumn: 8 } ] }, @@ -231,7 +286,10 @@ ruleTester.run("consistent-return", rule, { messageId: "missingReturn", data: { name: "method 'foo'" }, type: "FunctionExpression", - column: 12 + line: 1, + column: 12, + endLine: 1, + endColumn: 15 } ] }, @@ -243,7 +301,10 @@ ruleTester.run("consistent-return", rule, { messageId: "missingReturn", data: { name: "method 'foo'" }, type: "FunctionExpression", - column: 10 + line: 1, + column: 10, + endLine: 1, + endColumn: 13 } ] }, @@ -255,7 +316,10 @@ ruleTester.run("consistent-return", rule, { messageId: "missingReturn", data: { name: "program" }, type: "Program", - column: 1 + line: 1, + column: 1, + endLine: void 0, + endColumn: void 0 } ] }, @@ -267,7 +331,10 @@ ruleTester.run("consistent-return", rule, { messageId: "missingReturn", data: { name: "method 'CapitalizedFunction'" }, type: "FunctionExpression", - column: 11 + line: 1, + column: 11, + endLine: 1, + endColumn: 30 } ] }, @@ -279,7 +346,10 @@ ruleTester.run("consistent-return", rule, { messageId: "missingReturn", data: { name: "method 'constructor'" }, type: "FunctionExpression", - column: 4 + line: 1, + column: 4, + endLine: 1, + endColumn: 15 } ] } diff --git a/eslint/tests/lib/rules/curly.js b/eslint/tests/lib/rules/curly.js index 155eec9..e5fe882 100644 --- a/eslint/tests/lib/rules/curly.js +++ b/eslint/tests/lib/rules/curly.js @@ -13,7 +13,7 @@ const rule = require("../../../lib/rules/curly"), { RuleTester } = require("../../../lib/rule-tester"); //------------------------------------------------------------------------------ -// Helpers +// Tests //------------------------------------------------------------------------------ const ruleTester = new RuleTester(); @@ -429,7 +429,26 @@ ruleTester.run("curly", rule, { { messageId: "missingCurlyAfterCondition", data: { name: "if" }, - type: "IfStatement" + type: "IfStatement", + line: 1, + column: 10, + endLine: 1, + endColumn: 15 + } + ] + }, + { + code: "if (foo) \n bar()", + output: "if (foo) \n {bar()}", + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + line: 2, + column: 2, + endLine: 2, + endColumn: 7 } ] }, @@ -462,7 +481,26 @@ ruleTester.run("curly", rule, { { messageId: "missingCurlyAfterCondition", data: { name: "while" }, - type: "WhileStatement" + type: "WhileStatement", + line: 1, + column: 13, + endLine: 1, + endColumn: 18 + } + ] + }, + { + code: "while (foo) \n bar()", + output: "while (foo) \n {bar()}", + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "while" }, + type: "WhileStatement", + line: 2, + column: 2, + endLine: 2, + endColumn: 7 } ] }, @@ -473,7 +511,26 @@ ruleTester.run("curly", rule, { { messageId: "missingCurlyAfter", data: { name: "do" }, - type: "DoWhileStatement" + type: "DoWhileStatement", + line: 1, + column: 4, + endLine: 1, + endColumn: 10 + } + ] + }, + { + code: "do \n bar(); while (foo)", + output: "do \n {bar();} while (foo)", + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "do" }, + type: "DoWhileStatement", + line: 2, + column: 2, + endLine: 2, + endColumn: 8 } ] }, @@ -507,7 +564,93 @@ ruleTester.run("curly", rule, { { messageId: "missingCurlyAfter", data: { name: "for-of" }, - type: "ForOfStatement" + type: "ForOfStatement", + line: 1, + column: 22, + endLine: 1, + endColumn: 38 + } + ] + }, + { + code: "for (var foo of bar) \n console.log(foo)", + output: "for (var foo of bar) \n {console.log(foo)}", + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "for-of" }, + type: "ForOfStatement", + line: 2, + column: 2, + endLine: 2, + endColumn: 18 + } + ] + }, + { + code: "for (a;;) console.log(foo)", + output: "for (a;;) {console.log(foo)}", + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "for" }, + type: "ForStatement", + line: 1, + column: 11, + endLine: 1, + endColumn: 27 + } + ] + }, + { + code: "for (a;;) \n console.log(foo)", + output: "for (a;;) \n {console.log(foo)}", + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "for" }, + type: "ForStatement", + line: 2, + column: 2, + endLine: 2, + endColumn: 18 + } + ] + }, + { + code: "for (var foo of bar) {console.log(foo)}", + output: "for (var foo of bar) console.log(foo)", + options: ["multi"], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "for-of" }, + type: "ForOfStatement", + line: 1, + column: 22, + endLine: 1, + endColumn: 40 + } + ] + }, + { + code: "do{foo();} while(bar);", + output: "do foo(); while(bar);", + options: ["multi"], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "do" }, + type: "DoWhileStatement", + line: 1, + column: 3, + endLine: 1, + endColumn: 11 } ] }, @@ -519,7 +662,26 @@ ruleTester.run("curly", rule, { { messageId: "unexpectedCurlyAfterCondition", data: { name: "for" }, - type: "ForStatement" + type: "ForStatement", + line: 1, + column: 13, + endLine: 1, + endColumn: 22 + } + ] + }, + { + code: "for (;foo;) \n bar()", + output: "for (;foo;) \n {bar()}", + errors: [ + { + data: { name: "for" }, + type: "ForStatement", + messageId: "missingCurlyAfterCondition", + line: 2, + column: 2, + endLine: 2, + endColumn: 7 } ] }, @@ -531,7 +693,11 @@ ruleTester.run("curly", rule, { { messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, - type: "IfStatement" + type: "IfStatement", + line: 1, + column: 10, + endLine: 1, + endColumn: 19 } ] }, @@ -567,7 +733,11 @@ ruleTester.run("curly", rule, { { messageId: "unexpectedCurlyAfterCondition", data: { name: "while" }, - type: "WhileStatement" + type: "WhileStatement", + line: 1, + column: 13, + endLine: 1, + endColumn: 22 } ] }, @@ -653,7 +823,9 @@ ruleTester.run("curly", rule, { data: { name: "else" }, type: "IfStatement", line: 6, - column: 3 + column: 8, + endLine: 11, + endColumn: 2 } ] }, @@ -665,7 +837,11 @@ ruleTester.run("curly", rule, { { messageId: "unexpectedCurlyAfter", data: { name: "for-in" }, - type: "ForInStatement" + type: "ForInStatement", + line: 1, + column: 22, + endLine: 1, + endColumn: 42 } ] }, @@ -690,7 +866,26 @@ ruleTester.run("curly", rule, { { messageId: "missingCurlyAfterCondition", data: { name: "if" }, - type: "IfStatement" + type: "IfStatement", + line: 2, + column: 2, + endLine: 2, + endColumn: 7 + } + ] + }, + { + code: "if (foo) baz()", + output: "if (foo) {baz()}", + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + line: 1, + column: 10, + endLine: 1, + endColumn: 15 } ] }, @@ -742,6 +937,22 @@ ruleTester.run("curly", rule, { } ] }, + { + code: "do foo(); while (bar)", + output: "do {foo();} while (bar)", + options: ["all"], + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "do" }, + type: "DoWhileStatement", + line: 1, + column: 4, + endLine: 1, + endColumn: 10 + } + ] + }, { code: "do \n foo(); \n while (bar)", output: "do \n {foo();} \n while (bar)", @@ -750,7 +961,27 @@ ruleTester.run("curly", rule, { { messageId: "missingCurlyAfter", data: { name: "do" }, - type: "DoWhileStatement" + type: "DoWhileStatement", + line: 2, + column: 2, + endLine: 2, + endColumn: 8 + } + ] + }, + { + code: "for (var foo in bar) {console.log(foo)}", + output: "for (var foo in bar) console.log(foo)", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "for-in" }, + type: "ForInStatement", + line: 1, + column: 22, + endLine: 1, + endColumn: 40 } ] }, @@ -787,7 +1018,11 @@ ruleTester.run("curly", rule, { { messageId: "missingCurlyAfter", data: { name: "for-of" }, - type: "ForOfStatement" + type: "ForOfStatement", + line: 2, + column: 2, + endLine: 2, + endColumn: 18 } ] }, @@ -910,7 +1145,45 @@ ruleTester.run("curly", rule, { { messageId: "unexpectedCurlyAfterCondition", data: { name: "for" }, - type: "ForStatement" + type: "ForStatement", + line: 1, + column: 27, + endLine: 3, + endColumn: 3 + } + ] + }, + { + code: "for (var foo in bar) if (foo) console.log(1); else console.log(2);", + output: "for (var foo in bar) {if (foo) console.log(1); else console.log(2);}", + options: ["all"], + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "for-in" }, + type: "ForInStatement", + line: 1, + column: 22, + endLine: 1, + endColumn: 67 + }, + { + messageId: "missingCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + line: 1, + column: 31, + endLine: 1, + endColumn: 46 + }, + { + messageId: "missingCurlyAfter", + data: { name: "else" }, + type: "IfStatement", + line: 1, + column: 52, + endLine: 1, + endColumn: 67 } ] }, @@ -922,7 +1195,11 @@ ruleTester.run("curly", rule, { { messageId: "missingCurlyAfter", data: { name: "for-in" }, - type: "ForInStatement" + type: "ForInStatement", + line: 2, + column: 2, + endLine: 3, + endColumn: 22 } ] }, @@ -996,7 +1273,11 @@ ruleTester.run("curly", rule, { { messageId: "unexpectedCurlyAfter", data: { name: "else" }, - type: "IfStatement" + type: "IfStatement", + line: 1, + column: 23, + endLine: 1, + endColumn: 33 } ] }, @@ -1041,6 +1322,70 @@ ruleTester.run("curly", rule, { } ] }, + { + code: "do\n{foo();} while (bar)", + output: "do\nfoo(); while (bar)", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "do" }, + type: "DoWhileStatement", + line: 2, + column: 1, + endLine: 2, + endColumn: 9 + } + ] + }, + { + code: "while (bar) { foo(); }", + output: "while (bar) foo(); ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "while" }, + type: "WhileStatement", + line: 1, + column: 13, + endLine: 1, + endColumn: 23 + } + ] + }, + { + code: "while (bar) \n{\n foo(); }", + output: "while (bar) \n\n foo(); ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "while" }, + type: "WhileStatement", + line: 2, + column: 1, + endLine: 3, + endColumn: 10 + } + ] + }, + { + code: "for (;;) { foo(); }", + output: "for (;;) foo(); ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "for" }, + type: "ForStatement", + line: 1, + column: 10, + endLine: 1, + endColumn: 20 + } + ] + }, { code: "do{[1, 2, 3].map(bar);} while (bar)", output: "do[1, 2, 3].map(bar); while (bar)", @@ -1313,7 +1658,132 @@ ruleTester.run("curly", rule, { code: "if (a) { while (cond) if (b) foo() } else bar();", output: "if (a) { while (cond) if (b) foo() } else {bar();}", options: ["multi", "consistent"], - errors: [{ messageId: "missingCurlyAfter", data: { name: "else" }, type: "IfStatement" }] + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "else" }, + type: "IfStatement", + line: 1, + column: 43, + endLine: 1, + endColumn: 49 + } + ] + }, + { + code: "if (a) while (cond) if (b) foo() \nelse\n {bar();}", + output: "if (a) while (cond) if (b) foo() \nelse\n bar();", + options: ["multi", "consistent"], + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "else" }, + type: "IfStatement", + line: 3, + column: 2, + endLine: 3, + endColumn: 10 + } + ] + }, + { + code: "if (a) foo() \nelse\n bar();", + output: "if (a) {foo()} \nelse\n {bar();}", + errors: [{ + type: "IfStatement", + messageId: "missingCurlyAfterCondition", + line: 1, + column: 8, + endLine: 1, + endColumn: 13 + }, + { + type: "IfStatement", + messageId: "missingCurlyAfter", + line: 3, + column: 2, + endLine: 3, + endColumn: 8 + }] + }, + { + code: "if (a) { while (cond) if (b) foo() } ", + output: "if (a) while (cond) if (b) foo() ", + options: ["multi", "consistent"], + errors: [{ + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + line: 1, + column: 8, + endLine: 1, + endColumn: 37 + }] + }, + { + code: "if(a) { if (b) foo(); } if (c) bar(); else if(foo){bar();}", + output: "if(a) if (b) foo(); if (c) bar(); else if(foo)bar();", + options: ["multi-or-nest"], + errors: [{ + type: "IfStatement", + data: { name: "if" }, + messageId: "unexpectedCurlyAfterCondition", + line: 1, + column: 7, + endLine: 1, + endColumn: 24 + }, + { + type: "IfStatement", + data: { name: "if" }, + messageId: "unexpectedCurlyAfterCondition", + line: 1, + column: 51, + endLine: 1, + endColumn: 59 + }] + }, + { + code: "if (true) [1, 2, 3]\n.bar()", + output: "if (true) {[1, 2, 3]\n.bar()}", + options: ["multi-line"], + errors: [{ + data: { name: "if" }, + type: "IfStatement", + messageId: "missingCurlyAfterCondition", + line: 1, + column: 11, + endLine: 2, + endColumn: 7 + }] + }, + { + code: "for(\n;\n;\n) {foo()}", + output: "for(\n;\n;\n) foo()", + options: ["multi"], + errors: [{ + data: { name: "for" }, + type: "ForStatement", + messageId: "unexpectedCurlyAfterCondition", + line: 4, + column: 3, + endLine: 4, + endColumn: 10 + }] + }, + { + code: "for(\n;\n;\n) \nfoo()\n", + output: "for(\n;\n;\n) \n{foo()}\n", + options: ["multi-line"], + errors: [{ + data: { name: "for" }, + type: "ForStatement", + messageId: "missingCurlyAfterCondition", + line: 5, + column: 1, + endLine: 5, + endColumn: 6 + }] }, { @@ -1330,6 +1800,46 @@ ruleTester.run("curly", rule, { { messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }, { messageId: "unexpectedCurlyAfterCondition", data: { name: "while" }, type: "WhileStatement" } ] + }, + { + code: "for(;;)foo()\n", + output: "for(;;){foo()}\n", + errors: [{ + data: { name: "for" }, + type: "ForStatement", + messageId: "missingCurlyAfterCondition", + line: 1, + column: 8, + endLine: 1, + endColumn: 13 + }] + }, + { + code: "for(var \ni \n in \n z)foo()\n", + output: "for(var \ni \n in \n z){foo()}\n", + errors: [{ + data: { name: "for-in" }, + type: "ForInStatement", + messageId: "missingCurlyAfter", + line: 4, + column: 4, + endLine: 4, + endColumn: 9 + }] + }, + { + code: "for(var i of \n z)\nfoo()\n", + output: "for(var i of \n z)\n{foo()}\n", + parserOptions: { ecmaVersion: 6 }, + errors: [{ + data: { name: "for-of" }, + type: "ForOfStatement", + messageId: "missingCurlyAfter", + line: 3, + column: 1, + endLine: 3, + endColumn: 6 + }] } ] }); diff --git a/eslint/tests/lib/rules/default-param-last.js b/eslint/tests/lib/rules/default-param-last.js index 05321f5..e628db3 100644 --- a/eslint/tests/lib/rules/default-param-last.js +++ b/eslint/tests/lib/rules/default-param-last.js @@ -4,9 +4,17 @@ */ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + const rule = require("../../../lib/rules/default-param-last"); const { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const SHOULD_BE_LAST = "shouldBeLast"; const ruleTester = new RuleTester({ diff --git a/eslint/tests/lib/rules/dot-location.js b/eslint/tests/lib/rules/dot-location.js index bd56e1f..d120657 100644 --- a/eslint/tests/lib/rules/dot-location.js +++ b/eslint/tests/lib/rules/dot-location.js @@ -198,6 +198,18 @@ ruleTester.run("dot-location", rule, { code: "obj?.[\nkey]", options: ["property"], parserOptions: { ecmaVersion: 2020 } + }, + + // Private properties + { + code: "class C { #a; foo() { this.\n#a; } }", + options: ["object"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #a; foo() { this\n.#a; } }", + options: ["property"], + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -386,6 +398,22 @@ ruleTester.run("dot-location", rule, { options: ["property"], parserOptions: { ecmaVersion: 2020 }, errors: [{ messageId: "expectedDotBeforeProperty" }] + }, + + // Private properties + { + code: "class C { #a; foo() { this\n.#a; } }", + output: "class C { #a; foo() { this.\n#a; } }", + options: ["object"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "expectedDotAfterObject" }] + }, + { + code: "class C { #a; foo() { this.\n#a; } }", + output: "class C { #a; foo() { this\n.#a; } }", + options: ["property"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "expectedDotBeforeProperty" }] } ] }); diff --git a/eslint/tests/lib/rules/dot-notation.js b/eslint/tests/lib/rules/dot-notation.js index 39f3a67..c2ced10 100644 --- a/eslint/tests/lib/rules/dot-notation.js +++ b/eslint/tests/lib/rules/dot-notation.js @@ -21,13 +21,17 @@ const ruleTester = new RuleTester(); /** * Quote a string in "double quotes" because it’s painful * with a double-quoted string literal - * @param {string} str The string to quote - * @returns {string} `"${str}"` + * @param {string} str The string to quote + * @returns {string} `"${str}"` */ function q(str) { return `"${str}"`; } +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + ruleTester.run("dot-notation", rule, { valid: [ "a.b;", @@ -58,7 +62,13 @@ ruleTester.run("dot-notation", rule, { "a[undefined];", "a[void 0];", "a[b()];", - { code: "a[/(?0)/];", parserOptions: { ecmaVersion: 2018 } } + { code: "a[/(?0)/];", parserOptions: { ecmaVersion: 2018 } }, + { code: "class C { foo() { this['#a'] } }", parserOptions: { ecmaVersion: 2022 } }, + { + code: "class C { #in; foo() { this.#in; } }", + options: [{ allowKeywords: false }], + parserOptions: { ecmaVersion: 2022 } + } ], invalid: [ { diff --git a/eslint/tests/lib/rules/eol-last.js b/eslint/tests/lib/rules/eol-last.js index 1e5d3eb..7474b01 100644 --- a/eslint/tests/lib/rules/eol-last.js +++ b/eslint/tests/lib/rules/eol-last.js @@ -55,48 +55,104 @@ ruleTester.run("eol-last", rule, { { code: "var a = 123;", output: "var a = 123;\n", - errors: [{ messageId: "missing", type: "Program" }] + errors: [{ + messageId: "missing", + type: "Program", + line: 1, + column: 13, + endLine: void 0, + endColumn: void 0 + }] }, { code: "var a = 123;\n ", output: "var a = 123;\n \n", - errors: [{ messageId: "missing", type: "Program" }] + errors: [{ + messageId: "missing", + type: "Program", + line: 2, + column: 4, + endLine: void 0, + endColumn: void 0 + }] }, { code: "var a = 123;\n", output: "var a = 123;", options: ["never"], - errors: [{ messageId: "unexpected", type: "Program" }] + errors: [{ + messageId: "unexpected", + type: "Program", + line: 1, + column: 13, + endLine: 2, + endColumn: 1 + }] }, { code: "var a = 123;\r\n", output: "var a = 123;", options: ["never"], - errors: [{ messageId: "unexpected", type: "Program" }] + errors: [{ + messageId: "unexpected", + type: "Program", + line: 1, + column: 13, + endLine: 2, + endColumn: 1 + }] }, { code: "var a = 123;\r\n\r\n", output: "var a = 123;", options: ["never"], - errors: [{ messageId: "unexpected", type: "Program" }] + errors: [{ + messageId: "unexpected", + type: "Program", + line: 2, + column: 1, + endLine: 3, + endColumn: 1 + }] }, { code: "var a = 123;\nvar b = 456;\n", output: "var a = 123;\nvar b = 456;", options: ["never"], - errors: [{ messageId: "unexpected", type: "Program" }] + errors: [{ + messageId: "unexpected", + type: "Program", + line: 2, + column: 13, + endLine: 3, + endColumn: 1 + }] }, { code: "var a = 123;\r\nvar b = 456;\r\n", output: "var a = 123;\r\nvar b = 456;", options: ["never"], - errors: [{ messageId: "unexpected", type: "Program" }] + errors: [{ + messageId: "unexpected", + type: "Program", + line: 2, + column: 13, + endLine: 3, + endColumn: 1 + }] }, { code: "var a = 123;\n\n", output: "var a = 123;", options: ["never"], - errors: [{ messageId: "unexpected", type: "Program" }] + errors: [{ + messageId: "unexpected", + type: "Program", + line: 2, + column: 1, + endLine: 3, + endColumn: 1 + }] }, // Deprecated: `"unix"` parameter @@ -104,13 +160,27 @@ ruleTester.run("eol-last", rule, { code: "var a = 123;", output: "var a = 123;\n", options: ["unix"], - errors: [{ messageId: "missing", type: "Program" }] + errors: [{ + messageId: "missing", + type: "Program", + line: 1, + column: 13, + endLine: void 0, + endColumn: void 0 + }] }, { code: "var a = 123;\n ", output: "var a = 123;\n \n", options: ["unix"], - errors: [{ messageId: "missing", type: "Program" }] + errors: [{ + messageId: "missing", + type: "Program", + line: 2, + column: 4, + endLine: void 0, + endColumn: void 0 + }] }, // Deprecated: `"windows"` parameter @@ -118,13 +188,27 @@ ruleTester.run("eol-last", rule, { code: "var a = 123;", output: "var a = 123;\r\n", options: ["windows"], - errors: [{ messageId: "missing", type: "Program" }] + errors: [{ + messageId: "missing", + type: "Program", + line: 1, + column: 13, + endLine: void 0, + endColumn: void 0 + }] }, { code: "var a = 123;\r\n ", output: "var a = 123;\r\n \r\n", options: ["windows"], - errors: [{ messageId: "missing", type: "Program" }] + errors: [{ + messageId: "missing", + type: "Program", + line: 2, + column: 4, + endLine: void 0, + endColumn: void 0 + }] } ] }); diff --git a/eslint/tests/lib/rules/for-direction.js b/eslint/tests/lib/rules/for-direction.js index a04b10e..8ecd843 100644 --- a/eslint/tests/lib/rules/for-direction.js +++ b/eslint/tests/lib/rules/for-direction.js @@ -13,7 +13,7 @@ const rule = require("../../../lib/rules/for-direction"); const { RuleTester } = require("../../../lib/rule-tester"); //------------------------------------------------------------------------------ -// Helpers +// Tests //------------------------------------------------------------------------------ const ruleTester = new RuleTester(); diff --git a/eslint/tests/lib/rules/func-name-matching.js b/eslint/tests/lib/rules/func-name-matching.js index 908e910..7e21525 100644 --- a/eslint/tests/lib/rules/func-name-matching.js +++ b/eslint/tests/lib/rules/func-name-matching.js @@ -262,6 +262,248 @@ ruleTester.run("func-name-matching", rule, { { code: "foo({ value: function value() {} })", options: ["always", { considerPropertyDescriptor: true }] + }, + + // class fields, private names are ignored + { + code: "class C { x = function () {}; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { x = function () {}; }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { 'x' = function () {}; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { 'x' = function () {}; }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #x = function () {}; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #x = function () {}; }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { [x] = function () {}; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { [x] = function () {}; }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { ['x'] = function () {}; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { ['x'] = function () {}; }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { x = function x() {}; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { x = function y() {}; }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { 'x' = function x() {}; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { 'x' = function y() {}; }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #x = function x() {}; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #x = function x() {}; }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #x = function y() {}; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #x = function y() {}; }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { [x] = function x() {}; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { [x] = function x() {}; }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { [x] = function y() {}; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { [x] = function y() {}; }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { ['x'] = function x() {}; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { ['x'] = function y() {}; }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { 'xy ' = function foo() {}; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { 'xy ' = function xy() {}; }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { ['xy '] = function foo() {}; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { ['xy '] = function xy() {}; }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { 1 = function x0() {}; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { 1 = function x1() {}; }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { [1] = function x0() {}; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { [1] = function x1() {}; }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { [f()] = function g() {}; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { [f()] = function f() {}; }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static x = function x() {}; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static x = function y() {}; }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { x = (function y() {})(); }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { x = (function x() {})(); }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "(class { x = function x() {}; })", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "(class { x = function y() {}; })", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #x; foo() { this.#x = function x() {}; } }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #x; foo() { this.#x = function x() {}; } }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #x; foo() { this.#x = function y() {}; } }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #x; foo() { this.#x = function y() {}; } }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #x; foo() { a.b.#x = function x() {}; } }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #x; foo() { a.b.#x = function x() {}; } }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #x; foo() { a.b.#x = function y() {}; } }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #x; foo() { a.b.#x = function y() {}; } }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -554,6 +796,88 @@ ruleTester.run("func-name-matching", rule, { errors: [ { messageId: "notMatchProperty", data: { funcName: "bar", name: "bar" } } ] + }, + + // class fields + { + code: "class C { x = function y() {}; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "matchProperty", data: { funcName: "y", name: "x" } } + ] + }, + { + code: "class C { x = function x() {}; }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "notMatchProperty", data: { funcName: "x", name: "x" } } + ] + }, + { + code: "class C { 'x' = function y() {}; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "matchProperty", data: { funcName: "y", name: "x" } } + ] + }, + { + code: "class C { 'x' = function x() {}; }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "notMatchProperty", data: { funcName: "x", name: "x" } } + ] + }, + { + code: "class C { ['x'] = function y() {}; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "matchProperty", data: { funcName: "y", name: "x" } } + ] + }, + { + code: "class C { ['x'] = function x() {}; }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "notMatchProperty", data: { funcName: "x", name: "x" } } + ] + }, + { + code: "class C { static x = function y() {}; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "matchProperty", data: { funcName: "y", name: "x" } } + ] + }, + { + code: "class C { static x = function x() {}; }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "notMatchProperty", data: { funcName: "x", name: "x" } } + ] + }, + { + code: "(class { x = function y() {}; })", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "matchProperty", data: { funcName: "y", name: "x" } } + ] + }, + { + code: "(class { x = function x() {}; })", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "notMatchProperty", data: { funcName: "x", name: "x" } } + ] } ] }); diff --git a/eslint/tests/lib/rules/func-names.js b/eslint/tests/lib/rules/func-names.js index bf930ee..3341df7 100644 --- a/eslint/tests/lib/rules/func-names.js +++ b/eslint/tests/lib/rules/func-names.js @@ -262,6 +262,23 @@ ruleTester.run("func-names", rule, { code: "(function*() {}())", options: ["as-needed", { generators: "never" }], parserOptions: { ecmaVersion: 6 } + }, + + // class fields + { + code: "class C { foo = function() {}; }", + options: ["as-needed"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { [foo] = function() {}; }", + options: ["as-needed"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #foo = function() {}; }", + options: ["as-needed"], + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -805,6 +822,63 @@ ruleTester.run("func-names", rule, { column: 15, endColumn: 28 }] + }, + + // class fields + { + code: "class C { foo = function() {} }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "unnamed", + data: { name: "method 'foo'" }, + column: 11, + endColumn: 25 + }] + }, + { + code: "class C { [foo] = function() {} }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "unnamed", + data: { name: "method" }, + column: 11, + endColumn: 27 + }] + }, + { + code: "class C { #foo = function() {} }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "unnamed", + data: { name: "private method #foo" }, + column: 11, + endColumn: 26 + }] + }, + { + code: "class C { foo = bar(function() {}) }", + options: ["as-needed"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "unnamed", + data: { name: "function" }, + column: 21, + endColumn: 29 + }] + }, + { + code: "class C { foo = function bar() {} }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "named", + data: { name: "method 'foo'" }, + column: 11, + endColumn: 29 + }] } ] }); diff --git a/eslint/tests/lib/rules/getter-return.js b/eslint/tests/lib/rules/getter-return.js index c4092d8..92032c0 100644 --- a/eslint/tests/lib/rules/getter-return.js +++ b/eslint/tests/lib/rules/getter-return.js @@ -16,7 +16,7 @@ const { RuleTester } = require("../../../lib/rule-tester"); // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); const expectedError = { messageId: "expected", data: { name: "getter 'bar'" } }; const expectedAlwaysError = { messageId: "expectedAlways", data: { name: "getter 'bar'" } }; const options = [{ allowImplicit: true }]; @@ -72,7 +72,8 @@ ruleTester.run("getter-return", rule, { "var foo = { bar: function(){return;} };", "var foo = { bar: function(){return true;} };", "var foo = { get: function () {} }", - "var foo = { get: () => {}};" + "var foo = { get: () => {}};", + "class C { get; foo() {} }" ], invalid: [ @@ -187,7 +188,7 @@ ruleTester.run("getter-return", rule, { code: "Object.defineProperty(foo, 'bar', { get: function getfoo (){}});", errors: [{ messageId: "expected", - data: { name: "method 'getfoo'" }, + data: { name: "method 'get'" }, line: 1, column: 37, endLine: 1, @@ -209,11 +210,11 @@ ruleTester.run("getter-return", rule, { code: "Object.defineProperty(foo, 'bar', { get: () => {}});", errors: [{ messageId: "expected", - data: { name: "arrow function 'get'" }, + data: { name: "method 'get'" }, line: 1, - column: 45, + column: 37, endLine: 1, - endColumn: 47 + endColumn: 42 }] }, { code: "Object.defineProperty(foo, \"bar\", { get: function (){if(bar) {return true;}}});", errors: [{ messageId: "expectedAlways" }] }, diff --git a/eslint/tests/lib/rules/global-require.js b/eslint/tests/lib/rules/global-require.js index 4993962..df8337d 100644 --- a/eslint/tests/lib/rules/global-require.js +++ b/eslint/tests/lib/rules/global-require.js @@ -15,6 +15,7 @@ const rule = require("../../../lib/rules/global-require"), //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ + const ruleTester = new RuleTester(); const valid = [ diff --git a/eslint/tests/lib/rules/grouped-accessor-pairs.js b/eslint/tests/lib/rules/grouped-accessor-pairs.js index e94c3a4..75aa3a0 100644 --- a/eslint/tests/lib/rules/grouped-accessor-pairs.js +++ b/eslint/tests/lib/rules/grouped-accessor-pairs.js @@ -16,7 +16,7 @@ const { RuleTester } = require("../../../lib/rule-tester"); // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); ruleTester.run("grouped-accessor-pairs", rule, { valid: [ @@ -134,7 +134,19 @@ ruleTester.run("grouped-accessor-pairs", rule, { "({ get a(){}, b: 1, set a(foo){}, c: 2, get a(){} })", "({ set a(foo){}, b: 1, set 'a'(bar){}, c: 2, get a(){} })", "class A { get [a](){} b(){} get [a](){} c(){} set [a](foo){} }", - "(class { static set a(foo){} b(){} static get a(){} static c(){} static set a(bar){} })" + "(class { static set a(foo){} b(){} static get a(){} static c(){} static set a(bar){} })", + + // public and private + "class A { get '#abc'(){} b(){} set #abc(foo){} }", + "class A { get #abc(){} b(){} set '#abc'(foo){} }", + { + code: "class A { set '#abc'(foo){} get #abc(){} }", + options: ["getBeforeSet"] + }, + { + code: "class A { set #abc(foo){} get '#abc'(){} }", + options: ["getBeforeSet"] + } ], invalid: [ @@ -172,6 +184,14 @@ ruleTester.run("grouped-accessor-pairs", rule, { code: "class A { static get [a](){} b(){} static set [a](foo){} }", errors: [{ messageId: "notGrouped", data: { formerName: "static getter", latterName: "static setter" }, type: "MethodDefinition", column: 36 }] }, + { + code: "class A { get '#abc'(){} b(){} set '#abc'(foo){} }", + errors: [{ messageId: "notGrouped", data: { formerName: "getter '#abc'", latterName: "setter '#abc'" }, type: "MethodDefinition", column: 32 }] + }, + { + code: "class A { get #abc(){} b(){} set #abc(foo){} }", + errors: [{ messageId: "notGrouped", data: { formerName: "private getter #abc", latterName: "private setter #abc" }, type: "MethodDefinition", column: 30 }] + }, // basic ordering tests with full messages { @@ -214,6 +234,16 @@ ruleTester.run("grouped-accessor-pairs", rule, { options: ["getBeforeSet"], errors: [{ messageId: "invalidOrder", data: { formerName: "static setter", latterName: "static getter" }, type: "MethodDefinition", column: 35 }] }, + { + code: "class A { set '#abc'(foo){} get '#abc'(){} }", + options: ["getBeforeSet"], + errors: [{ messageId: "invalidOrder", data: { latterName: "getter '#abc'", formerName: "setter '#abc'" }, type: "MethodDefinition", column: 29 }] + }, + { + code: "class A { set #abc(foo){} get #abc(){} }", + options: ["getBeforeSet"], + errors: [{ messageId: "invalidOrder", data: { latterName: "private getter #abc", formerName: "private setter #abc" }, type: "MethodDefinition", column: 27 }] + }, // ordering option does not affect the grouping check { @@ -406,6 +436,11 @@ ruleTester.run("grouped-accessor-pairs", rule, { code: "class A { get a(){} a(){} set a(foo){} }", errors: [{ messageId: "notGrouped", data: { formerName: "getter 'a'", latterName: "setter 'a'" }, type: "MethodDefinition", column: 27 }] }, + { + code: "class A { get a(){} a; set a(foo){} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "notGrouped", data: { formerName: "getter 'a'", latterName: "setter 'a'" }, type: "MethodDefinition", column: 24 }] + }, // full location tests { diff --git a/eslint/tests/lib/rules/id-denylist.js b/eslint/tests/lib/rules/id-denylist.js index 6da179d..4e82483 100644 --- a/eslint/tests/lib/rules/id-denylist.js +++ b/eslint/tests/lib/rules/id-denylist.js @@ -204,6 +204,18 @@ ruleTester.run("id-denylist", rule, { code: "var foo = { bar: window.baz };", options: ["window"], env: { browser: true } + }, + + // Class fields + { + code: "class C { camelCase; #camelCase; #camelCase2() {} }", + options: ["foo"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { snake_case; #snake_case; #snake_case2() {} }", + options: ["foo"], + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -1354,6 +1366,44 @@ ruleTester.run("id-denylist", rule, { type: "Identifier" } ] + }, + + // Class fields + { + code: "class C { camelCase; #camelCase; #camelCase2() {} }", + options: ["camelCase"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "restricted", + data: { name: "camelCase" }, + type: "Identifier" + }, + { + messageId: "restrictedPrivate", + data: { name: "camelCase" }, + type: "PrivateIdentifier" + } + ] + + }, + { + code: "class C { snake_case; #snake_case() {}; #snake_case2() {} }", + options: ["snake_case"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "restricted", + data: { name: "snake_case" }, + type: "Identifier" + }, + { + messageId: "restrictedPrivate", + data: { name: "snake_case" }, + type: "PrivateIdentifier" + } + ] + } ] }); diff --git a/eslint/tests/lib/rules/id-length.js b/eslint/tests/lib/rules/id-length.js index 99a3957..e9a023b 100644 --- a/eslint/tests/lib/rules/id-length.js +++ b/eslint/tests/lib/rules/id-length.js @@ -18,7 +18,9 @@ const rule = require("../../../lib/rules/id-length"), const ruleTester = new RuleTester(); const tooShortError = { messageId: "tooShort", type: "Identifier" }; +const tooShortErrorPrivate = { messageId: "tooShortPrivate", type: "PrivateIdentifier" }; const tooLongError = { messageId: "tooLong", type: "Identifier" }; +const tooLongErrorPrivate = { messageId: "tooLongPrivate", type: "PrivateIdentifier" }; ruleTester.run("id-length", rule, { valid: [ @@ -82,7 +84,36 @@ ruleTester.run("id-length", rule, { { code: "function BEFORE_send() {};", options: [{ min: 3, max: 5, exceptionPatterns: ["^BEFORE_", "send$"] }] }, { code: "function BEFORE_send() {};", options: [{ min: 3, max: 5, exceptionPatterns: ["^BEFORE_", "^A", "^Z"] }] }, { code: "function BEFORE_send() {};", options: [{ min: 3, max: 5, exceptionPatterns: ["^A", "^BEFORE_", "^Z"] }] }, - { code: "var x = 1 ;", options: [{ min: 3, max: 5, exceptionPatterns: ["[x-z]"] }] } + { code: "var x = 1 ;", options: [{ min: 3, max: 5, exceptionPatterns: ["[x-z]"] }] }, + + // Class Fields + { + code: "class Foo { #xyz() {} }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class Foo { xyz = 1 }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class Foo { #xyz = 1 }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class Foo { #abc() {} }", + options: [{ max: 3 }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class Foo { abc = 1 }", + options: [{ max: 3 }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class Foo { #abc = 1 }", + options: [{ max: 3 }], + parserOptions: { ecmaVersion: 2022 } + } ], invalid: [ { code: "var x = 1;", errors: [tooShortError] }, @@ -486,6 +517,53 @@ ruleTester.run("id-length", rule, { errors: [ tooShortError ] + }, + + // Class Fields + { + code: "class Foo { #x() {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + tooShortErrorPrivate + ] + }, + { + code: "class Foo { x = 1 }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + tooShortError + ] + }, + { + code: "class Foo { #x = 1 }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + tooShortErrorPrivate + ] + }, + { + code: "class Foo { #abcdefg() {} }", + options: [{ max: 3 }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + tooLongErrorPrivate + ] + }, + { + code: "class Foo { abcdefg = 1 }", + options: [{ max: 3 }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + tooLongError + ] + }, + { + code: "class Foo { #abcdefg = 1 }", + options: [{ max: 3 }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + tooLongErrorPrivate + ] } ] }); diff --git a/eslint/tests/lib/rules/id-match.js b/eslint/tests/lib/rules/id-match.js index f733dcc..d1fad2e 100644 --- a/eslint/tests/lib/rules/id-match.js +++ b/eslint/tests/lib/rules/id-match.js @@ -183,7 +183,36 @@ ruleTester.run("id-match", rule, { options: ["^[^_]+$", { properties: false }] + }, + + // Class Methods + { + code: "class x { foo() {} }", + options: ["^[^_]+$"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class x { #foo() {} }", + options: ["^[^_]+$"], + parserOptions: { ecmaVersion: 2022 } + }, + + // Class Fields + { + code: "class x { _foo = 1; }", + options: ["^[^_]+$", { + classFields: false + }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class x { #_foo = 1; }", + options: ["^[^_]+$", { + classFields: false + }], + parserOptions: { ecmaVersion: 2022 } } + ], invalid: [ { @@ -610,6 +639,59 @@ ruleTester.run("id-match", rule, { type: "Identifier" } ] + }, + + // Class Methods + { + code: "class x { _foo() {} }", + options: ["^[^_]+$"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + message: "Identifier '_foo' does not match the pattern '^[^_]+$'.", + type: "Identifier" + } + ] + }, + { + code: "class x { #_foo() {} }", + options: ["^[^_]+$"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + message: "Identifier '#_foo' does not match the pattern '^[^_]+$'.", + type: "PrivateIdentifier" + } + ] + }, + + // Class Fields + { + code: "class x { _foo = 1; }", + options: ["^[^_]+$", { + classFields: true + }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + message: "Identifier '_foo' does not match the pattern '^[^_]+$'.", + type: "Identifier" + } + ] + }, + { + code: "class x { #_foo = 1; }", + options: ["^[^_]+$", { + classFields: true + }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + message: "Identifier '#_foo' does not match the pattern '^[^_]+$'.", + type: "PrivateIdentifier" + } + ] } + ] }); diff --git a/eslint/tests/lib/rules/indent.js b/eslint/tests/lib/rules/indent.js index 02517ff..3947679 100644 --- a/eslint/tests/lib/rules/indent.js +++ b/eslint/tests/lib/rules/indent.js @@ -15,7 +15,7 @@ const fs = require("fs"); const path = require("path"); //------------------------------------------------------------------------------ -// Tests +// Helpers //------------------------------------------------------------------------------ const fixture = fs.readFileSync(path.join(__dirname, "../../fixtures/rules/indent/indent-invalid-fixture-1.js"), "utf8"); @@ -56,6 +56,10 @@ function expectedErrors(providedIndentType, providedErrors) { })); } +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 8, ecmaFeatures: { jsx: true } } }); ruleTester.run("indent", rule, { @@ -5843,6 +5847,241 @@ ruleTester.run("indent", rule, { ar2){} `, options: [2, { FunctionDeclaration: { parameters: "first" }, FunctionExpression: { parameters: "first" } }] + }, + { + code: unIndent` + class C { + static { + foo(); + bar(); + } + } + `, + options: [2], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C { + static { + foo(); + bar(); + } + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C { + static { + foo(); + bar(); + } + } + `, + options: [4, { StaticBlock: { body: 2 } }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C { + static { + foo(); + bar(); + } + } + `, + options: [4, { StaticBlock: { body: 0 } }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C { + \tstatic { + \t\tfoo(); + \t\tbar(); + \t} + } + `, + options: ["tab"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C { + \tstatic { + \t\t\tfoo(); + \t\t\tbar(); + \t} + } + `, + options: ["tab", { StaticBlock: { body: 2 } }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C { + static + { + foo(); + bar(); + } + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C { + static { + var x, + y; + } + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C { + static + { + var x, + y; + } + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C { + static { + if (foo) { + bar; + } + } + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C { + static { + { + bar; + } + } + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C { + static {} + + static { + } + + static + { + } + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C { + + static { + foo; + } + + static { + bar; + } + + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C { + + x = 1; + + static { + foo; + } + + y = 2; + + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C { + + method1(param) { + foo; + } + + static { + bar; + } + + method2(param) { + foo; + } + + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + function f() { + class C { + static { + foo(); + bar(); + } + } + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C { + method() { + foo; + } + static { + bar; + } + } + `, + options: [4, { FunctionExpression: { body: 2 }, StaticBlock: { body: 2 } }], + parserOptions: { ecmaVersion: 2022 } } ], @@ -11627,6 +11866,763 @@ ruleTester.run("indent", rule, { errors: expectedErrors([ [2, 0, 1, "Identifier"] ]) + }, + { + code: unIndent` + class C { + field1; + static field2; + } + `, + output: unIndent` + class C { + field1; + static field2; + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [2, 4, 0, "Identifier"], + [3, 4, 0, "Keyword"] + ]) + }, + { + code: unIndent` + class C { + field1 + = + 0 + ; + static + field2 + = + 0 + ; + } + `, + output: unIndent` + class C { + field1 + = + 0 + ; + static + field2 + = + 0 + ; + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [2, 4, 0, "Identifier"], + [3, 8, 0, "Punctuator"], + [4, 12, 0, "Numeric"], + [5, 12, 0, "Punctuator"], + [6, 4, 0, "Keyword"], + [7, 8, 0, "Identifier"], + [8, 12, 0, "Punctuator"], + [9, 16, 0, "Numeric"], + [10, 16, 0, "Punctuator"] + ]) + }, + { + code: unIndent` + class C { + [ + field1 + ] + = + 0 + ; + static + [ + field2 + ] + = + 0 + ; + [ + field3 + ] = + 0; + [field4] = + 0; + } + `, + output: unIndent` + class C { + [ + field1 + ] + = + 0 + ; + static + [ + field2 + ] + = + 0 + ; + [ + field3 + ] = + 0; + [field4] = + 0; + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [2, 4, 0, "Punctuator"], + [3, 8, 0, "Identifier"], + [4, 4, 0, "Punctuator"], + [5, 8, 0, "Punctuator"], + [6, 12, 0, "Numeric"], + [7, 12, 0, "Punctuator"], + [8, 4, 0, "Keyword"], + [9, 4, 0, "Punctuator"], + [10, 8, 0, "Identifier"], + [11, 4, 0, "Punctuator"], + [12, 8, 0, "Punctuator"], + [13, 12, 0, "Numeric"], + [14, 12, 0, "Punctuator"], + [15, 4, 0, "Punctuator"], + [16, 8, 0, "Identifier"], + [17, 4, 0, "Punctuator"], + [18, 8, 0, "Numeric"], + [19, 4, 0, "Punctuator"], + [20, 8, 0, "Numeric"] + ]) + }, + { + code: unIndent` + class C { + field1 = ( + foo + + bar + ); + } + `, + output: unIndent` + class C { + field1 = ( + foo + + bar + ); + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [2, 4, 0, "Identifier"], + [3, 8, 0, "Identifier"], + [5, 4, 0, "Punctuator"] + ]) + }, + { + code: unIndent` + class C { + #aaa + foo() { + return this.#aaa + } + } + `, + output: unIndent` + class C { + #aaa + foo() { + return this.#aaa + } + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [2, 4, 0, "PrivateIdentifier"], + [3, 4, 0, "Identifier"], + [4, 8, 0, "Keyword"], + [5, 4, 0, "Punctuator"] + ]) + }, + { + code: unIndent` + class C { + static { + foo(); + bar(); + } + } + `, + output: unIndent` + class C { + static { + foo(); + bar(); + } + } + `, + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [2, 2, 0, "Keyword"], + [3, 4, 0, "Identifier"], + [4, 4, 0, "Identifier"], + [5, 2, 0, "Punctuator"] + ]) + }, + { + code: unIndent` + class C { + static { + foo(); + bar(); + } + } + `, + output: unIndent` + class C { + static { + foo(); + bar(); + } + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [2, 4, 0, "Keyword"], + [3, 8, 0, "Identifier"], + [4, 8, 0, "Identifier"], + [5, 4, 0, "Punctuator"] + ]) + }, + { + code: unIndent` + class C { + static { + foo(); + bar(); + } + } + `, + output: unIndent` + class C { + static { + foo(); + bar(); + } + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [2, 4, 8, "Keyword"], + [3, 8, 4, "Identifier"], + [4, 8, 0, "Identifier"], + [5, 4, 8, "Punctuator"] + ]) + }, + { + code: unIndent` + class C { + static { + foo(); + bar(); + } + } + `, + output: unIndent` + class C { + static { + foo(); + bar(); + } + } + `, + options: [4, { StaticBlock: { body: 2 } }], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [2, 4, 0, "Keyword"], + [3, 12, 0, "Identifier"], + [4, 12, 0, "Identifier"], + [5, 4, 0, "Punctuator"] + ]) + }, + { + code: unIndent` + class C { + static { + foo(); + bar(); + } + } + `, + output: unIndent` + class C { + static { + foo(); + bar(); + } + } + `, + options: [4, { StaticBlock: { body: 0 } }], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [2, 4, 0, "Keyword"], + [3, 4, 0, "Identifier"], + [4, 4, 0, "Identifier"], + [5, 4, 0, "Punctuator"] + ]) + }, + { + code: unIndent` + class C { + static { + foo(); + bar(); + } + } + `, + output: unIndent` + class C { + \tstatic { + \t\tfoo(); + \t\tbar(); + \t} + } + `, + options: ["tab"], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors("tab", [ + [2, 1, 0, "Keyword"], + [3, 2, 0, "Identifier"], + [4, 2, 0, "Identifier"], + [5, 1, 0, "Punctuator"] + ]) + }, + { + code: unIndent` + class C { + static { + foo(); + bar(); + } + } + `, + output: unIndent` + class C { + \tstatic { + \t\t\tfoo(); + \t\t\tbar(); + \t} + } + `, + options: ["tab", { StaticBlock: { body: 2 } }], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors("tab", [ + [2, 1, 0, "Keyword"], + [3, 3, 0, "Identifier"], + [4, 3, 0, "Identifier"], + [5, 1, 0, "Punctuator"] + ]) + }, + { + code: unIndent` + class C { + static + { + foo(); + bar(); + } + } + `, + output: unIndent` + class C { + static + { + foo(); + bar(); + } + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [2, 4, 0, "Keyword"], + [3, 4, 0, "Punctuator"], + [4, 8, 0, "Identifier"], + [5, 8, 0, "Identifier"], + [6, 4, 0, "Punctuator"] + ]) + }, + { + code: unIndent` + class C { + static + { + foo(); + bar(); + } + } + `, + output: unIndent` + class C { + static + { + foo(); + bar(); + } + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [3, 4, 8, "Punctuator"], + [6, 4, 8, "Punctuator"] + ]) + }, + { + code: unIndent` + class C { + static { + var x, + y; + } + } + `, + output: unIndent` + class C { + static { + var x, + y; + } + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [2, 4, 0, "Keyword"], + [3, 8, 0, "Keyword"], + [4, 12, 0, "Identifier"], + [5, 4, 0, "Punctuator"] + ]) + }, + { + code: unIndent` + class C { + static + { + var x, + y; + } + } + `, + output: unIndent` + class C { + static + { + var x, + y; + } + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [2, 4, 0, "Keyword"], + [3, 4, 0, "Punctuator"], + [4, 8, 0, "Keyword"], + [5, 12, 0, "Identifier"], + [6, 4, 0, "Punctuator"] + ]) + }, + { + code: unIndent` + class C { + static { + if (foo) { + bar; + } + } + } + `, + output: unIndent` + class C { + static { + if (foo) { + bar; + } + } + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [2, 4, 0, "Keyword"], + [3, 8, 0, "Keyword"], + [4, 12, 0, "Identifier"], + [5, 8, 0, "Punctuator"], + [6, 4, 0, "Punctuator"] + ]) + }, + { + code: unIndent` + class C { + static { + { + bar; + } + } + } + `, + output: unIndent` + class C { + static { + { + bar; + } + } + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [2, 4, 0, "Keyword"], + [3, 8, 0, "Punctuator"], + [4, 12, 0, "Identifier"], + [5, 8, 0, "Punctuator"], + [6, 4, 0, "Punctuator"] + ]) + }, + { + code: unIndent` + class C { + static {} + + static { + } + + static + { + } + } + `, + output: unIndent` + class C { + static {} + + static { + } + + static + { + } + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [2, 4, 0, "Keyword"], + [4, 4, 0, "Keyword"], + [5, 4, 0, "Punctuator"], + [7, 4, 0, "Keyword"], + [8, 4, 0, "Punctuator"], + [9, 4, 0, "Punctuator"] + ]) + }, + { + code: unIndent` + class C { + + static { + foo; + } + + static { + bar; + } + + } + `, + output: unIndent` + class C { + + static { + foo; + } + + static { + bar; + } + + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [3, 4, 0, "Keyword"], + [4, 8, 4, "Identifier"], + [5, 4, 0, "Punctuator"], + [7, 4, 0, "Keyword"], + [8, 8, 4, "Identifier"], + [9, 4, 0, "Punctuator"] + ]) + }, + { + code: unIndent` + class C { + + x = 1; + + static { + foo; + } + + y = 2; + + } + `, + output: unIndent` + class C { + + x = 1; + + static { + foo; + } + + y = 2; + + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [3, 4, 0, "Identifier"], + [5, 4, 0, "Keyword"], + [6, 8, 4, "Identifier"], + [7, 4, 0, "Punctuator"], + [9, 4, 0, "Identifier"] + ]) + }, + { + code: unIndent` + class C { + + method1(param) { + foo; + } + + static { + bar; + } + + method2(param) { + foo; + } + + } + `, + output: unIndent` + class C { + + method1(param) { + foo; + } + + static { + bar; + } + + method2(param) { + foo; + } + + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [3, 4, 0, "Identifier"], + [4, 8, 4, "Identifier"], + [5, 4, 0, "Punctuator"], + [7, 4, 0, "Keyword"], + [8, 8, 4, "Identifier"], + [9, 4, 0, "Punctuator"], + [11, 4, 0, "Identifier"], + [12, 8, 4, "Identifier"], + [13, 4, 0, "Punctuator"] + ]) + }, + { + code: unIndent` + function f() { + class C { + static { + foo(); + bar(); + } + } + } + `, + output: unIndent` + function f() { + class C { + static { + foo(); + bar(); + } + } + } + `, + options: [4], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [2, 4, 0, "Keyword"], + [3, 8, 0, "Keyword"], + [4, 12, 0, "Identifier"], + [5, 12, 0, "Identifier"], + [6, 8, 0, "Punctuator"], + [7, 4, 0, "Punctuator"] + ]) + }, + { + code: unIndent` + class C { + method() { + foo; + } + static { + bar; + } + } + `, + output: unIndent` + class C { + method() { + foo; + } + static { + bar; + } + } + `, + options: [4, { FunctionExpression: { body: 2 }, StaticBlock: { body: 2 } }], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedErrors([ + [2, 4, 0, "Identifier"], + [3, 12, 0, "Identifier"], + [4, 4, 0, "Punctuator"], + [5, 4, 0, "Keyword"], + [6, 12, 0, "Identifier"], + [7, 4, 0, "Punctuator"] + ]) } ] }); diff --git a/eslint/tests/lib/rules/init-declarations.js b/eslint/tests/lib/rules/init-declarations.js index 6324418..556086a 100644 --- a/eslint/tests/lib/rules/init-declarations.js +++ b/eslint/tests/lib/rules/init-declarations.js @@ -12,6 +12,10 @@ const rule = require("../../../lib/rules/init-declarations"), { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester(); ruleTester.run("init-declarations", rule, { diff --git a/eslint/tests/lib/rules/jsx-quotes.js b/eslint/tests/lib/rules/jsx-quotes.js index f4c72d9..cdc1b04 100644 --- a/eslint/tests/lib/rules/jsx-quotes.js +++ b/eslint/tests/lib/rules/jsx-quotes.js @@ -12,6 +12,10 @@ const rule = require("../../../lib/rules/jsx-quotes"), { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } }); ruleTester.run("jsx-quotes", rule, { diff --git a/eslint/tests/lib/rules/key-spacing.js b/eslint/tests/lib/rules/key-spacing.js index 849ce61..fde9673 100644 --- a/eslint/tests/lib/rules/key-spacing.js +++ b/eslint/tests/lib/rules/key-spacing.js @@ -12,6 +12,10 @@ const rule = require("../../../lib/rules/key-spacing"), { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester(); ruleTester.run("key-spacing", rule, { diff --git a/eslint/tests/lib/rules/keyword-spacing.js b/eslint/tests/lib/rules/keyword-spacing.js index 64ee5a4..35c8a06 100644 --- a/eslint/tests/lib/rules/keyword-spacing.js +++ b/eslint/tests/lib/rules/keyword-spacing.js @@ -11,7 +11,8 @@ const parser = require("../../fixtures/fixture-parser"), rule = require("../../../lib/rules/keyword-spacing"), - { RuleTester } = require("../../../lib/rule-tester"); + { RuleTester } = require("../../../lib/rule-tester"), + fixtureParser = require("../../fixtures/fixture-parser"); //------------------------------------------------------------------------------ // Helpers @@ -259,6 +260,12 @@ ruleTester.run("keyword-spacing", rule, { // not conflict with `space-infix-ops` { code: "async function wrap() { a =await a }", parserOptions: { ecmaVersion: 8 } }, { code: "async function wrap() { a = await a }", options: [NEITHER], parserOptions: { ecmaVersion: 8 } }, + { code: "async function wrap() { a+await a }", parserOptions: { ecmaVersion: 8 } }, + { code: "async function wrap() { a + await a }", options: [NEITHER], parserOptions: { ecmaVersion: 8 } }, + { code: "async function wrap() { aawait a }", parserOptions: { ecmaVersion: 8 } }, + { code: "async function wrap() { a > await a }", options: [NEITHER], parserOptions: { ecmaVersion: 8 } }, // not conflict with `space-unary-ops` { code: "async function wrap() { !await'a' }", parserOptions: { ecmaVersion: 8 } }, @@ -366,6 +373,12 @@ ruleTester.run("keyword-spacing", rule, { // not conflict with `space-infix-ops` { code: "a =class {}", parserOptions: { ecmaVersion: 6 } }, { code: "a = class{}", options: [NEITHER], parserOptions: { ecmaVersion: 6 } }, + { code: "a+class {}", parserOptions: { ecmaVersion: 6 } }, + { code: "a + class{}", options: [NEITHER], parserOptions: { ecmaVersion: 6 } }, + { code: "aclass {}", parserOptions: { ecmaVersion: 6 } }, + { code: "a > class{}", options: [NEITHER], parserOptions: { ecmaVersion: 6 } }, // not conflict with `space-unary-ops` { code: "!class {}", parserOptions: { ecmaVersion: 6 } }, @@ -379,6 +392,10 @@ ruleTester.run("keyword-spacing", rule, { { code: "", parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } }, { code: "", options: [NEITHER], parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } }, + // private names + { code: "class C {\n#x;\nfoo() {\nfor (this.#x of bar){}}}", options: [{ before: false }], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C {\n#x;\nfoo() {\nfor (this.#x in bar){}}}", options: [{ before: false }], parserOptions: { ecmaVersion: 2022 } }, + //---------------------------------------------------------------------- // const //---------------------------------------------------------------------- @@ -497,6 +514,12 @@ ruleTester.run("keyword-spacing", rule, { // not conflict with `space-infix-ops` "a =delete foo.a", { code: "a = delete foo.a", options: [NEITHER] }, + "a+delete foo.a", + { code: "a + delete foo.a", options: [NEITHER] }, + "adelete foo.a", + { code: "a > delete foo.a", options: [NEITHER] }, // not conflict with `space-unary-ops` "!delete(foo.a)", @@ -694,6 +717,12 @@ ruleTester.run("keyword-spacing", rule, { // not conflict with `space-infix-ops` "a =function() {}", { code: "a = function() {}", options: [NEITHER] }, + "a+function() {}", + { code: "a + function() {}", options: [NEITHER] }, + "afunction() {}", + { code: "a > function() {}", options: [NEITHER] }, // not conflict with `space-unary-ops` "!function() {}", @@ -721,11 +750,17 @@ ruleTester.run("keyword-spacing", rule, { { code: "class A { a() {} get [b]() {} }", options: [override("get", BOTH)], parserOptions: { ecmaVersion: 6 } }, { code: "({ get[b]() {} })", options: [override("get", NEITHER)], parserOptions: { ecmaVersion: 6 } }, { code: "class A { a() {}get[b]() {} }", options: [override("get", NEITHER)], parserOptions: { ecmaVersion: 6 } }, + { code: "class A { a; get #b() {} }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { a;get#b() {} }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } }, // not conflict with `comma-spacing` { code: "({ a,get [b]() {} })", parserOptions: { ecmaVersion: 6 } }, { code: "({ a, get[b]() {} })", options: [NEITHER], parserOptions: { ecmaVersion: 6 } }, + // not conflict with `semi-spacing` + { code: "class A { ;get #b() {} }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { ; get#b() {} }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } }, + //---------------------------------------------------------------------- // if //---------------------------------------------------------------------- @@ -850,6 +885,12 @@ ruleTester.run("keyword-spacing", rule, { // not conflict with `space-infix-ops` "a =new foo()", { code: "a = new foo()", options: [NEITHER] }, + "a+new foo()", + { code: "a + new foo()", options: [NEITHER] }, + "anew foo()", + { code: "a > new foo()", options: [NEITHER] }, // not conflict with `space-unary-ops` "!new(foo)()", @@ -878,7 +919,16 @@ ruleTester.run("keyword-spacing", rule, { //---------------------------------------------------------------------- "function foo() { {} return +a }", + { + code: "function foo() { return

; }", + parserOptions: { ecmaFeatures: { jsx: true } } + }, { code: "function foo() { {}return+a }", options: [NEITHER] }, + { + code: "function foo() { return

; }", + options: [{ after: false }], + parserOptions: { ecmaFeatures: { jsx: true } } + }, { code: "function foo() { {} return +a }", options: [override("return", BOTH)] }, { code: "function foo() { {}return+a }", options: [override("return", NEITHER)] }, "function foo() {\nreturn\n}", @@ -905,11 +955,17 @@ ruleTester.run("keyword-spacing", rule, { { code: "class A { a() {} set [b](value) {} }", options: [override("set", BOTH)], parserOptions: { ecmaVersion: 6 } }, { code: "({ set[b](value) {} })", options: [override("set", NEITHER)], parserOptions: { ecmaVersion: 6 } }, { code: "class A { a() {}set[b](value) {} }", options: [override("set", NEITHER)], parserOptions: { ecmaVersion: 6 } }, + { code: "class A { a; set #b(value) {} }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { a;set#b(value) {} }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } }, // not conflict with `comma-spacing` { code: "({ a,set [b](value) {} })", parserOptions: { ecmaVersion: 6 } }, { code: "({ a, set[b](value) {} })", options: [NEITHER], parserOptions: { ecmaVersion: 6 } }, + // not conflict with `semi-spacing` + { code: "class A { ;set #b(value) {} }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { ; set#b(value) {} }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } }, + //---------------------------------------------------------------------- // static //---------------------------------------------------------------------- @@ -918,6 +974,15 @@ ruleTester.run("keyword-spacing", rule, { { code: "class A { a() {}static[b]() {} }", options: [NEITHER], parserOptions: { ecmaVersion: 6 } }, { code: "class A { a() {} static [b]() {} }", options: [override("static", BOTH)], parserOptions: { ecmaVersion: 6 } }, { code: "class A { a() {}static[b]() {} }", options: [override("static", NEITHER)], parserOptions: { ecmaVersion: 6 } }, + { code: "class A { a; static [b]; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { a;static[b]; }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { a; static #b; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { a;static#b; }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { a() {} static {} }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { a() {}static{} }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { a() {} static {} }", options: [override("static", BOTH)], parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { a() {}static{} }", options: [override("static", NEITHER)], parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { a() {}\nstatic\n{} }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } }, // not conflict with `generator-star-spacing` { code: "class A { static* [a]() {} }", parserOptions: { ecmaVersion: 6 } }, @@ -926,6 +991,10 @@ ruleTester.run("keyword-spacing", rule, { // not conflict with `semi-spacing` { code: "class A { ;static a() {} }", parserOptions: { ecmaVersion: 6 } }, { code: "class A { ; static a() {} }", options: [NEITHER], parserOptions: { ecmaVersion: 6 } }, + { code: "class A { ;static a; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { ; static a ; }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { ;static {} }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { ; static{} }", options: [NEITHER], parserOptions: { ecmaVersion: 2022 } }, //---------------------------------------------------------------------- // super @@ -975,6 +1044,12 @@ ruleTester.run("keyword-spacing", rule, { // not conflict with `space-infix-ops` { code: "class A extends B { constructor() { b =super() } }", parserOptions: { ecmaVersion: 6 } }, { code: "class A extends B { constructor() { b = super() } }", options: [NEITHER], parserOptions: { ecmaVersion: 6 } }, + { code: "class A extends B { constructor() { b+super() } }", parserOptions: { ecmaVersion: 6 } }, + { code: "class A extends B { constructor() { b + super() } }", options: [NEITHER], parserOptions: { ecmaVersion: 6 } }, + { code: "class A extends B { constructor() { bsuper() } }", parserOptions: { ecmaVersion: 6 } }, + { code: "class A extends B { constructor() { b > super() } }", options: [NEITHER], parserOptions: { ecmaVersion: 6 } }, // not conflict with `space-unary-ops` { code: "class A extends B { constructor() { !super() } }", parserOptions: { ecmaVersion: 6 } }, @@ -1014,6 +1089,16 @@ ruleTester.run("keyword-spacing", rule, { { code: "{} this[a]", options: [override("this", BOTH)] }, { code: "{}this[a]", options: [override("this", NEITHER)] }, + { + code: " this.blah", + parser: fixtureParser("keyword-spacing", "prefix-cast-operator-space") + }, + { + code: "this.blah", + options: [override("this", { before: false })], + parser: fixtureParser("keyword-spacing", "prefix-cast-operator-no-space") + }, + // not conflict with `array-bracket-spacing` "[this]", { code: "[ this ]", options: [NEITHER] }, @@ -1051,6 +1136,18 @@ ruleTester.run("keyword-spacing", rule, { // not conflict with `space-infix-ops` "a =this", { code: "a = this", options: [NEITHER] }, + "a+this", + { code: "a + this", options: [NEITHER] }, + "athis", + { code: "a > this", options: [NEITHER] }, + "this+a", + { code: "this + a", options: [NEITHER] }, + "thisa", + { code: "this > a", options: [NEITHER] }, // not conflict with `space-unary-ops` "!this", @@ -1146,6 +1243,12 @@ ruleTester.run("keyword-spacing", rule, { // not conflict with `space-infix-ops` "a =typeof foo", { code: "a = typeof foo", options: [NEITHER] }, + "a+typeof foo", + { code: "a + typeof foo", options: [NEITHER] }, + "atypeof foo", + { code: "a > typeof foo", options: [NEITHER] }, // not conflict with `space-unary-ops` "!typeof+foo", @@ -1223,6 +1326,12 @@ ruleTester.run("keyword-spacing", rule, { // not conflict with `space-infix-ops` "a =void foo", { code: "a = void foo", options: [NEITHER] }, + "a+void foo", + { code: "a + void foo", options: [NEITHER] }, + "avoid foo", + { code: "a > void foo", options: [NEITHER] }, // not conflict with `space-unary-ops` "!void+foo", @@ -2453,6 +2562,19 @@ ruleTester.run("keyword-spacing", rule, { parserOptions: { ecmaVersion: 6 }, errors: unexpectedBeforeAndAfter("get") }, + { + code: "class A { a;get#b() {} }", + output: "class A { a;get #b() {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: expectedAfter("get") + }, + { + code: "class A { a; get #b() {} }", + output: "class A { a; get#b() {} }", + options: [NEITHER], + parserOptions: { ecmaVersion: 2022 }, + errors: unexpectedAfter("get") + }, //---------------------------------------------------------------------- // if @@ -2750,12 +2872,25 @@ ruleTester.run("keyword-spacing", rule, { output: "function foo() { {} return +a }", errors: expectedBeforeAndAfter("return") }, + { + code: "function foo() { return

; }", + output: "function foo() { return

; }", + parserOptions: { ecmaFeatures: { jsx: true } }, + errors: expectedAfter("return") + }, { code: "function foo() { {} return +a }", output: "function foo() { {}return+a }", options: [NEITHER], errors: unexpectedBeforeAndAfter("return") }, + { + code: "function foo() { return

; }", + output: "function foo() { return

; }", + options: [{ after: false }], + parserOptions: { ecmaFeatures: { jsx: true } }, + errors: unexpectedAfter("return") + }, { code: "function foo() { {}return+a }", output: "function foo() { {} return +a }", @@ -2833,6 +2968,19 @@ ruleTester.run("keyword-spacing", rule, { parserOptions: { ecmaVersion: 6 }, errors: unexpectedBeforeAndAfter("set") }, + { + code: "class A { a;set#b(x) {} }", + output: "class A { a;set #b(x) {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: expectedAfter("set") + }, + { + code: "class A { a; set #b(x) {} }", + output: "class A { a; set#b(x) {} }", + options: [NEITHER], + parserOptions: { ecmaVersion: 2022 }, + errors: unexpectedAfter("set") + }, //---------------------------------------------------------------------- // static @@ -2878,6 +3026,85 @@ ruleTester.run("keyword-spacing", rule, { parserOptions: { ecmaVersion: 6 }, errors: unexpectedBeforeAndAfter("static") }, + { + code: "class A { a;static[b]; }", + output: "class A { a;static [b]; }", + parserOptions: { ecmaVersion: 2022 }, + errors: expectedAfter("static") + }, + { + code: "class A { a; static [b]; }", + output: "class A { a; static[b]; }", + options: [NEITHER], + parserOptions: { ecmaVersion: 2022 }, + errors: unexpectedAfter("static") + }, + { + code: "class A { a;static#b; }", + output: "class A { a;static #b; }", + parserOptions: { ecmaVersion: 2022 }, + errors: expectedAfter("static") + }, + { + code: "class A { a; static #b; }", + output: "class A { a; static#b; }", + options: [NEITHER], + parserOptions: { ecmaVersion: 2022 }, + errors: unexpectedAfter("static") + }, + { + code: "class A { a() {}static{} }", + output: "class A { a() {} static {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: expectedBeforeAndAfter("static") + }, + { + code: "class A { a() {}static{} }", + output: "class A { a() {} static {} }", + options: [override("static", BOTH)], + parserOptions: { ecmaVersion: 2022 }, + errors: expectedBeforeAndAfter("static") + }, + { + code: "class A { a() {}static {} }", + output: "class A { a() {} static {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: expectedBefore("static") + }, + { + code: "class A { a() {} static{} }", + output: "class A { a() {} static {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: expectedAfter("static") + }, + { + code: "class A { a() {} static {} }", + output: "class A { a() {}static{} }", + options: [NEITHER], + parserOptions: { ecmaVersion: 2022 }, + errors: unexpectedBeforeAndAfter("static") + }, + { + code: "class A { a() {} static {} }", + output: "class A { a() {}static{} }", + options: [override("static", NEITHER)], + parserOptions: { ecmaVersion: 2022 }, + errors: unexpectedBeforeAndAfter("static") + }, + { + code: "class A { a() {} static{} }", + output: "class A { a() {}static{} }", + options: [NEITHER], + parserOptions: { ecmaVersion: 2022 }, + errors: unexpectedBefore("static") + }, + { + code: "class A { a() {}static {} }", + output: "class A { a() {}static{} }", + options: [NEITHER], + parserOptions: { ecmaVersion: 2022 }, + errors: unexpectedAfter("static") + }, //---------------------------------------------------------------------- // super @@ -2966,6 +3193,19 @@ ruleTester.run("keyword-spacing", rule, { options: [override("this", NEITHER)], errors: unexpectedBefore("this") }, + { + code: " this.blah", + output: "this.blah", + options: [override("this", { before: false })], + parser: fixtureParser("keyword-spacing", "prefix-cast-operator-space"), + errors: unexpectedBefore("this") + }, + { + code: "this.blah", + output: " this.blah", + parser: fixtureParser("keyword-spacing", "prefix-cast-operator-no-space"), + errors: expectedBefore("this") + }, //---------------------------------------------------------------------- // throw diff --git a/eslint/tests/lib/rules/lines-around-comment.js b/eslint/tests/lib/rules/lines-around-comment.js index a4fd3dd..2687f14 100644 --- a/eslint/tests/lib/rules/lines-around-comment.js +++ b/eslint/tests/lib/rules/lines-around-comment.js @@ -9,7 +9,8 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/lines-around-comment"), - { RuleTester } = require("../../../lib/rule-tester"); + { RuleTester } = require("../../../lib/rule-tester"), + { unIndent } = require("../../_utils"); //------------------------------------------------------------------------------ // Tests @@ -262,6 +263,106 @@ ruleTester.run("lines-around-comment", rule, { allowBlockStart: true }] }, + { + code: unIndent` + class C { + static { + // line comment + } + + static { + // line comment + foo(); + } + }`, + options: [{ + beforeLineComment: true, + allowBlockStart: true + }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C { + static + { + // line comment + } + + static + { + // line comment + foo(); + } + }`, + options: [{ + beforeLineComment: true, + allowBlockStart: true + }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C { + static { + /* block comment */ + } + + static { + /* block + comment */ + } + + static { + /* block comment */ + foo(); + } + + static { + /* block + comment */ + foo(); + } + }`, + options: [{ + beforeBlockComment: true, + allowBlockStart: true + }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C { + static + { + /* block comment */ + } + + static + { + /* block + comment */ + } + + static + { + /* block comment */ + foo(); + } + + static + { + /* block + comment */ + foo(); + } + }`, + options: [{ + beforeBlockComment: true, + allowBlockStart: true + }], + parserOptions: { ecmaVersion: 2022 } + }, // check for block end comments { @@ -472,6 +573,54 @@ ruleTester.run("lines-around-comment", rule, { allowBlockEnd: true }] }, + { + code: unIndent` + class C { + static { + // line comment + } + + static { + foo(); + // line comment + } + }`, + options: [{ + afterLineComment: true, + allowBlockEnd: true + }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: unIndent` + class C { + static { + /* block comment */ + } + + static { + /* block + comment */ + } + + static { + foo(); + /* block comment */ + } + + static { + foo(); + /* block + comment */ + } + }`, + options: [{ + beforeBlockComment: false, // default is `true` + afterBlockComment: true, + allowBlockEnd: true + }], + parserOptions: { ecmaVersion: 2022 } + }, // check for object start comments { @@ -1049,6 +1198,346 @@ ruleTester.run("lines-around-comment", rule, { }], errors: [{ messageId: "after", type: "Line", line: 8 }] }, + { + code: unIndent` + class C { + // line comment + static{} + }`, + output: unIndent` + class C { + // line comment + + static{} + }`, + options: [{ + beforeLineComment: true, + afterLineComment: true, + allowBlockStart: true, + allowBlockEnd: true, + allowClassStart: true, + allowClassEnd: true + }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "after", type: "Line", line: 2 } + ] + }, + { + code: unIndent` + class C { + /* block + comment */ + static{} + }`, + output: unIndent` + class C { + /* block + comment */ + + static{} + }`, + options: [{ + beforeBlockComment: true, + afterBlockComment: true, + allowBlockStart: true, + allowBlockEnd: true, + allowClassStart: true, + allowClassEnd: true + }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "after", type: "Block", line: 2 } + ] + }, + { + code: unIndent` + class C { + static + // line comment + {} + }`, + output: unIndent` + class C { + static + + // line comment + + {} + }`, + options: [{ + beforeLineComment: true, + afterLineComment: true, + allowBlockStart: true, + allowBlockEnd: true, + allowClassStart: true, + allowClassEnd: true + }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "before", type: "Line", line: 3 }, + { messageId: "after", type: "Line", line: 3 } + ] + }, + { + code: unIndent` + class C { + static + /* block + comment */ + {} + }`, + output: unIndent` + class C { + static + + /* block + comment */ + + {} + }`, + options: [{ + beforeBlockComment: true, + afterBlockComment: true, + allowBlockStart: true, + allowBlockEnd: true, + allowClassStart: true, + allowClassEnd: true + }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "before", type: "Block", line: 3 }, + { messageId: "after", type: "Block", line: 3 } + ] + }, + { + code: unIndent` + class C { + static { + // line comment + foo(); + } + }`, + output: unIndent` + class C { + static { + // line comment + + foo(); + } + }`, + options: [{ + beforeLineComment: true, + afterLineComment: true, + allowBlockStart: true, + allowBlockEnd: true + }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "after", type: "Line", line: 3 } + ] + }, + { + code: unIndent` + class C { + static { + /* block + comment */ + foo(); + } + }`, + output: unIndent` + class C { + static { + /* block + comment */ + + foo(); + } + }`, + options: [{ + beforeBlockComment: true, + afterBlockComment: true, + allowBlockStart: true, + allowBlockEnd: true + }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "after", type: "Block", line: 3 } + ] + }, + { + code: unIndent` + class C { + static { + foo(); + // line comment + } + }`, + output: unIndent` + class C { + static { + foo(); + + // line comment + } + }`, + options: [{ + beforeLineComment: true, + afterLineComment: true, + allowBlockStart: true, + allowBlockEnd: true + }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "before", type: "Line", line: 4 } + ] + }, + { + code: unIndent` + class C { + static { + foo(); + /* block + comment */ + } + }`, + output: unIndent` + class C { + static { + foo(); + + /* block + comment */ + } + }`, + options: [{ + beforeBlockComment: true, + afterBlockComment: true, + allowBlockStart: true, + allowBlockEnd: true + }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "before", type: "Block", line: 4 } + ] + }, + { + code: unIndent` + class C { + static { + foo(); + // line comment + bar(); + } + }`, + output: unIndent` + class C { + static { + foo(); + + // line comment + + bar(); + } + }`, + options: [{ + beforeLineComment: true, + afterLineComment: true, + allowBlockStart: true, + allowBlockEnd: true + }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "before", type: "Line", line: 4 }, + { messageId: "after", type: "Line", line: 4 } + ] + }, + { + code: unIndent` + class C { + static { + foo(); + /* block + comment */ + bar(); + } + }`, + output: unIndent` + class C { + static { + foo(); + + /* block + comment */ + + bar(); + } + }`, + options: [{ + beforeBlockComment: true, + afterBlockComment: true, + allowBlockStart: true, + allowBlockEnd: true + }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "before", type: "Block", line: 4 }, + { messageId: "after", type: "Block", line: 4 } + ] + }, + { + code: unIndent` + class C { + static{} + // line comment + }`, + output: unIndent` + class C { + static{} + + // line comment + }`, + options: [{ + beforeLineComment: true, + afterLineComment: true, + allowBlockStart: true, + allowBlockEnd: true, + allowClassStart: true, + allowClassEnd: true + }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "before", type: "Line", line: 3 } + ] + }, + { + code: unIndent` + class C { + static{} + /* block + comment */ + }`, + output: unIndent` + class C { + static{} + + /* block + comment */ + }`, + options: [{ + beforeBlockComment: true, + afterBlockComment: true, + allowBlockStart: true, + allowBlockEnd: true, + allowClassStart: true, + allowClassEnd: true + }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "before", type: "Block", line: 3 } + ] + }, // object start comments { diff --git a/eslint/tests/lib/rules/lines-between-class-members.js b/eslint/tests/lib/rules/lines-between-class-members.js index e4b1c0c..feb9c08 100644 --- a/eslint/tests/lib/rules/lines-between-class-members.js +++ b/eslint/tests/lib/rules/lines-between-class-members.js @@ -15,6 +15,7 @@ const { RuleTester } = require("../../../lib/rule-tester"); //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ + const alwaysError = { messageId: "always" }; const neverError = { messageId: "never" }; @@ -22,7 +23,7 @@ const neverError = { messageId: "never" }; // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); ruleTester.run("lines-between-class-members", rule, { valid: [ @@ -46,6 +47,8 @@ ruleTester.run("lines-between-class-members", rule, { "class foo{ bar(){}\n\n;;baz(){}}", "class foo{ bar(){};\n\nbaz(){}}", + "class C {\naaa;\n\n#bbb;\n\nccc(){}\n\n#ddd(){}\n}", + { code: "class foo{ bar(){}\nbaz(){}}", options: ["never"] }, { code: "class foo{ bar(){}\n/*comments*/baz(){}}", options: ["never"] }, { code: "class foo{ bar(){}\n//comments\nbaz(){}}", options: ["never"] }, @@ -58,7 +61,13 @@ ruleTester.run("lines-between-class-members", rule, { { code: "class foo{ bar(){}\n\n//comments\nbaz(){}}", options: ["always"] }, { code: "class foo{ bar(){}\nbaz(){}}", options: ["always", { exceptAfterSingleLine: true }] }, - { code: "class foo{ bar(){\n}\n\nbaz(){}}", options: ["always", { exceptAfterSingleLine: true }] } + { code: "class foo{ bar(){\n}\n\nbaz(){}}", options: ["always", { exceptAfterSingleLine: true }] }, + { code: "class foo{\naaa;\n#bbb;\nccc(){\n}\n\n#ddd(){\n}\n}", options: ["always", { exceptAfterSingleLine: true }] }, + + // semicolon-less style (semicolons are at the beginning of lines) + { code: "class C { foo\n\n;bar }", options: ["always"] }, + { code: "class C { foo\n;bar }", options: ["always", { exceptAfterSingleLine: true }] }, + { code: "class C { foo\n;bar }", options: ["never"] } ], invalid: [ { @@ -141,6 +150,64 @@ ruleTester.run("lines-between-class-members", rule, { output: "class A {\nfoo() {}\n\n/* comment */;\n;\nbar() {}\n}", options: ["always"], errors: [alwaysError] + }, { + code: "class C {\nfield1\nfield2\n}", + output: "class C {\nfield1\n\nfield2\n}", + options: ["always"], + errors: [alwaysError] + }, { + code: "class C {\n#field1\n#field2\n}", + output: "class C {\n#field1\n\n#field2\n}", + options: ["always"], + errors: [alwaysError] + }, { + code: "class C {\nfield1\n\nfield2\n}", + output: "class C {\nfield1\nfield2\n}", + options: ["never"], + errors: [neverError] + }, { + code: "class C {\nfield1 = () => {\n}\nfield2\nfield3\n}", + output: "class C {\nfield1 = () => {\n}\n\nfield2\nfield3\n}", + options: ["always", { exceptAfterSingleLine: true }], + errors: [alwaysError] + }, + { + code: "class C { foo;bar }", + output: "class C { foo;\nbar }", + options: ["always"], + errors: [alwaysError] + }, + { + code: "class C { foo;\nbar; }", + output: "class C { foo;\n\nbar; }", + options: ["always"], + errors: [alwaysError] + }, + { + code: "class C { foo;\n;bar }", + output: "class C { foo;\n\n;bar }", + options: ["always"], + errors: [alwaysError] + }, + + // semicolon-less style (semicolons are at the beginning of lines) + { + code: "class C { foo\n;bar }", + output: "class C { foo\n\n;bar }", + options: ["always"], + errors: [alwaysError] + }, + { + code: "class C { foo\n\n;bar }", + output: "class C { foo\n;bar }", + options: ["never"], + errors: [neverError] + }, + { + code: "class C { foo\n;;bar }", + output: "class C { foo\n\n;;bar }", + options: ["always"], + errors: [alwaysError] } ] }); diff --git a/eslint/tests/lib/rules/max-classes-per-file.js b/eslint/tests/lib/rules/max-classes-per-file.js index ca9cba7..b5e8726 100644 --- a/eslint/tests/lib/rules/max-classes-per-file.js +++ b/eslint/tests/lib/rules/max-classes-per-file.js @@ -29,6 +29,29 @@ ruleTester.run("max-classes-per-file", rule, { { code: "class Foo {}\nclass Bar {}", options: [2] + }, + { + code: "class Foo {}", + options: [{ max: 1 }] + }, + { + code: "class Foo {}\nclass Bar {}", + options: [{ max: 2 }] + }, + { + code: ` + class Foo {} + const myExpression = class {} + `, + options: [{ ignoreExpressions: true, max: 1 }] + }, + { + code: ` + class Foo {} + class Bar {} + const myExpression = class {} + `, + options: [{ ignoreExpressions: true, max: 2 }] } ], @@ -37,6 +60,10 @@ ruleTester.run("max-classes-per-file", rule, { code: "class Foo {}\nclass Bar {}", errors: [{ messageId: "maximumExceeded", type: "Program" }] }, + { + code: "class Foo {}\nconst myExpression = class {}", + errors: [{ messageId: "maximumExceeded", type: "Program" }] + }, { code: "var x = class {};\nvar y = class {};", errors: [{ messageId: "maximumExceeded", type: "Program" }] @@ -54,6 +81,25 @@ ruleTester.run("max-classes-per-file", rule, { code: "class Foo {} class Bar {} class Baz {}", options: [2], errors: [{ messageId: "maximumExceeded", type: "Program" }] + }, + { + code: ` + class Foo {} + class Bar {} + const myExpression = class {} + `, + options: [{ ignoreExpressions: true, max: 1 }], + errors: [{ messageId: "maximumExceeded", type: "Program" }] + }, + { + code: ` + class Foo {} + class Bar {} + class Baz {} + const myExpression = class {} + `, + options: [{ ignoreExpressions: true, max: 2 }], + errors: [{ messageId: "maximumExceeded", type: "Program" }] } ] }); diff --git a/eslint/tests/lib/rules/max-depth.js b/eslint/tests/lib/rules/max-depth.js index 0b4f846..b35b603 100644 --- a/eslint/tests/lib/rules/max-depth.js +++ b/eslint/tests/lib/rules/max-depth.js @@ -26,7 +26,18 @@ ruleTester.run("max-depth", rule, { "function foo() { if (true) { if (false) { if (true) { } } } }", // object property options - { code: "function foo() { if (true) { if (false) { if (true) { } } } }", options: [{ max: 3 }] } + { code: "function foo() { if (true) { if (false) { if (true) { } } } }", options: [{ max: 3 }] }, + + { code: "class C { static { if (1) { if (2) {} } } }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { if (1) { if (2) {} } if (1) { if (2) {} } } }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { if (1) { if (2) {} } } static { if (1) { if (2) {} } } }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "if (1) { class C { static { if (1) { if (2) {} } } } }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "function foo() { if (1) { class C { static { if (1) { if (2) {} } } } } }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { + code: "function foo() { if (1) { if (2) { class C { static { if (1) { if (2) {} } if (1) { if (2) {} } } } } } if (1) { if (2) {} } }", + options: [2], + parserOptions: { ecmaVersion: 2022 } + } ], invalid: [ { code: "function foo() { if (true) { if (false) { if (true) { } } } }", options: [2], errors: [{ messageId: "tooDeeply", data: { depth: 3, maxDepth: 2 }, type: "IfStatement" }] }, @@ -41,6 +52,51 @@ ruleTester.run("max-depth", rule, { { code: "function foo() { if (true) { if (false) { if (true) { } } } }", options: [{ max: 2 }], errors: [{ messageId: "tooDeeply", data: { depth: 3, maxDepth: 2 }, type: "IfStatement" }] }, { code: "function foo() { if (a) { if (b) { if (c) { if (d) { if (e) {} } } } } }", options: [{}], errors: [{ messageId: "tooDeeply", data: { depth: 5, maxDepth: 4 } }] }, - { code: "function foo() { if (true) {} }", options: [{ max: 0 }], errors: [{ messageId: "tooDeeply", data: { depth: 1, maxDepth: 0 } }] } + { code: "function foo() { if (true) {} }", options: [{ max: 0 }], errors: [{ messageId: "tooDeeply", data: { depth: 1, maxDepth: 0 } }] }, + + { + code: "class C { static { if (1) { if (2) { if (3) {} } } } }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "tooDeeply", + data: { depth: 3, maxDepth: 2 }, + line: 1, + column: 38 + }] + }, + { + code: "if (1) { class C { static { if (1) { if (2) { if (3) {} } } } } }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "tooDeeply", + data: { depth: 3, maxDepth: 2 }, + line: 1, + column: 47 + }] + }, + { + code: "function foo() { if (1) { class C { static { if (1) { if (2) { if (3) {} } } } } } }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "tooDeeply", + data: { depth: 3, maxDepth: 2 }, + line: 1, + column: 64 + }] + }, + { + code: "function foo() { if (1) { class C { static { if (1) { if (2) {} } } } if (2) { if (3) {} } } }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "tooDeeply", + data: { depth: 3, maxDepth: 2 }, + line: 1, + column: 80 + }] + } ] }); diff --git a/eslint/tests/lib/rules/max-lines-per-function.js b/eslint/tests/lib/rules/max-lines-per-function.js index 008f2b0..ba01206 100644 --- a/eslint/tests/lib/rules/max-lines-per-function.js +++ b/eslint/tests/lib/rules/max-lines-per-function.js @@ -7,6 +7,7 @@ //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ + const rule = require("../../../lib/rules/max-lines-per-function"); const { RuleTester } = require("../../../lib/rule-tester"); diff --git a/eslint/tests/lib/rules/max-nested-callbacks.js b/eslint/tests/lib/rules/max-nested-callbacks.js index 2ea2def..64341b1 100644 --- a/eslint/tests/lib/rules/max-nested-callbacks.js +++ b/eslint/tests/lib/rules/max-nested-callbacks.js @@ -12,6 +12,10 @@ const rule = require("../../../lib/rules/max-nested-callbacks"), { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + const OPENING = "foo(function() {", CLOSING = "});"; diff --git a/eslint/tests/lib/rules/max-statements.js b/eslint/tests/lib/rules/max-statements.js index fa3d3d1..decdd8a 100644 --- a/eslint/tests/lib/rules/max-statements.js +++ b/eslint/tests/lib/rules/max-statements.js @@ -33,7 +33,33 @@ ruleTester.run("max-statements", rule, { { code: "var foo = { thing() { var bar = 1; var baz = 2; } }", options: [2], parserOptions: { ecmaVersion: 6 } }, { code: "var foo = { ['thing']() { var bar = 1; var baz = 2; } }", options: [2], parserOptions: { ecmaVersion: 6 } }, { code: "var foo = { thing: () => { var bar = 1; var baz = 2; } }", options: [2], parserOptions: { ecmaVersion: 6 } }, - { code: "var foo = { thing: function() { var bar = 1; var baz = 2; } }", options: [{ max: 2 }] } + { code: "var foo = { thing: function() { var bar = 1; var baz = 2; } }", options: [{ max: 2 }] }, + + // this rule does not apply to class static blocks, and statements in them should not count as statements in the enclosing function + { code: "class C { static { one; two; three; { four; five; six; } } }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "function foo() { class C { static { one; two; three; { four; five; six; } } } }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { one; two; three; function foo() { 1; 2; } four; five; six; } }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { { one; two; three; function foo() { 1; 2; } four; five; six; } } }", options: [2], parserOptions: { ecmaVersion: 2022 } }, + { + code: "function top_level() { 1; /* 2 */ class C { static { one; two; three; { four; five; six; } } } 3;}", + options: [2, { ignoreTopLevelFunctions: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "function top_level() { 1; 2; } class C { static { one; two; three; { four; five; six; } } }", + options: [1, { ignoreTopLevelFunctions: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { one; two; three; { four; five; six; } } } function top_level() { 1; 2; } ", + options: [1, { ignoreTopLevelFunctions: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "function foo() { let one; let two = class { static { let three; let four; let five; if (six) { let seven; let eight; let nine; } } }; }", + options: [2], + parserOptions: { ecmaVersion: 2022 } + } ], invalid: [ { @@ -127,7 +153,7 @@ ruleTester.run("max-statements", rule, { code: "var foo = { thing: () => { var bar = 1; var baz = 2; var baz2; } }", options: [2], parserOptions: { ecmaVersion: 6 }, - errors: [{ messageId: "exceed", data: { name: "Arrow function 'thing'", count: "3", max: 2 } }] + errors: [{ messageId: "exceed", data: { name: "Method 'thing'", count: "3", max: 2 } }] }, { code: "var foo = { thing: function() { var bar = 1; var baz = 2; var baz2; } }", @@ -143,6 +169,30 @@ ruleTester.run("max-statements", rule, { code: "function foo() { 1; }", options: [{ max: 0 }], errors: [{ messageId: "exceed", data: { name: "Function 'foo'", count: 1, max: 0 } }] + }, + { + code: "function foo() { foo_1; /* foo_ 2 */ class C { static { one; two; three; four; { five; six; seven; eight; } } } foo_3 }", + options: [2], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "exceed", data: { name: "Function 'foo'", count: 3, max: 2 } }] + }, + { + code: "class C { static { one; two; three; four; function not_top_level() { 1; 2; 3; } five; six; seven; eight; } }", + options: [2, { ignoreTopLevelFunctions: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "exceed", data: { name: "Function 'not_top_level'", count: 3, max: 2 } }] + }, + { + code: "class C { static { { one; two; three; four; function not_top_level() { 1; 2; 3; } five; six; seven; eight; } } }", + options: [2, { ignoreTopLevelFunctions: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "exceed", data: { name: "Function 'not_top_level'", count: 3, max: 2 } }] + }, + { + code: "class C { static { { one; two; three; four; } function not_top_level() { 1; 2; 3; } { five; six; seven; eight; } } }", + options: [2, { ignoreTopLevelFunctions: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "exceed", data: { name: "Function 'not_top_level'", count: 3, max: 2 } }] } ] }); diff --git a/eslint/tests/lib/rules/multiline-comment-style.js b/eslint/tests/lib/rules/multiline-comment-style.js index 6c8d469..1f2302f 100644 --- a/eslint/tests/lib/rules/multiline-comment-style.js +++ b/eslint/tests/lib/rules/multiline-comment-style.js @@ -11,7 +11,6 @@ const rule = require("../../../lib/rules/multiline-comment-style"); const { RuleTester } = require("../../../lib/rule-tester"); - //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ diff --git a/eslint/tests/lib/rules/new-parens.js b/eslint/tests/lib/rules/new-parens.js index 3508ace..2b93711 100644 --- a/eslint/tests/lib/rules/new-parens.js +++ b/eslint/tests/lib/rules/new-parens.js @@ -16,6 +16,7 @@ const parser = require("../../fixtures/fixture-parser"), //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ + const error = { messageId: "missing", type: "NewExpression" }; const neverError = { messageId: "unnecessary", type: "NewExpression" }; diff --git a/eslint/tests/lib/rules/newline-per-chained-call.js b/eslint/tests/lib/rules/newline-per-chained-call.js index 2a26937..ce7719b 100644 --- a/eslint/tests/lib/rules/newline-per-chained-call.js +++ b/eslint/tests/lib/rules/newline-per-chained-call.js @@ -12,6 +12,10 @@ const rule = require("../../../lib/rules/newline-per-chained-call"), { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester(); ruleTester.run("newline-per-chained-call", rule, { diff --git a/eslint/tests/lib/rules/no-await-in-loop.js b/eslint/tests/lib/rules/no-await-in-loop.js index 6ba1e7b..efca881 100644 --- a/eslint/tests/lib/rules/no-await-in-loop.js +++ b/eslint/tests/lib/rules/no-await-in-loop.js @@ -5,9 +5,17 @@ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + const rule = require("../../../lib/rules/no-await-in-loop"), { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const error = { messageId: "unexpectedAwait", type: "AwaitExpression" }; const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } }); diff --git a/eslint/tests/lib/rules/no-buffer-constructor.js b/eslint/tests/lib/rules/no-buffer-constructor.js index f45b4e4..14568f0 100644 --- a/eslint/tests/lib/rules/no-buffer-constructor.js +++ b/eslint/tests/lib/rules/no-buffer-constructor.js @@ -11,7 +11,6 @@ const rule = require("../../../lib/rules/no-buffer-constructor"); const { RuleTester } = require("../../../lib/rule-tester"); - //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ diff --git a/eslint/tests/lib/rules/no-compare-neg-zero.js b/eslint/tests/lib/rules/no-compare-neg-zero.js index b9c6303..68478dd 100644 --- a/eslint/tests/lib/rules/no-compare-neg-zero.js +++ b/eslint/tests/lib/rules/no-compare-neg-zero.js @@ -9,10 +9,8 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/no-compare-neg-zero"); - const { RuleTester } = require("../../../lib/rule-tester"); - //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ diff --git a/eslint/tests/lib/rules/no-dupe-class-members.js b/eslint/tests/lib/rules/no-dupe-class-members.js index a1a534d..a37784b 100644 --- a/eslint/tests/lib/rules/no-dupe-class-members.js +++ b/eslint/tests/lib/rules/no-dupe-class-members.js @@ -16,7 +16,7 @@ const { RuleTester } = require("../../../lib/rule-tester"); // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); ruleTester.run("no-dupe-class-members", rule, { valid: [ @@ -51,7 +51,12 @@ ruleTester.run("no-dupe-class-members", rule, { "class A { [-1]() {} ['-1']() {} }", // not supported by this rule - "class A { [foo]() {} [foo]() {} }" + "class A { [foo]() {} [foo]() {} }", + + // private and public + "class A { foo; static foo; }", + "class A { foo; #foo; }", + "class A { '#foo'; #foo; }" ], invalid: [ { @@ -217,6 +222,17 @@ ruleTester.run("no-dupe-class-members", rule, { errors: [ { type: "MethodDefinition", line: 1, column: 29, messageId: "unexpected", data: { name: "foo" } } ] + }, + { + code: "class A { foo; foo; }", + errors: [ + { type: "PropertyDefinition", line: 1, column: 16, messageId: "unexpected", data: { name: "foo" } } + ] } + + /* + * This is syntax error + * { code: "class A { #foo; #foo; }" } + */ ] }); diff --git a/eslint/tests/lib/rules/no-empty-character-class.js b/eslint/tests/lib/rules/no-empty-character-class.js index 1421c2a..fd4cef8 100644 --- a/eslint/tests/lib/rules/no-empty-character-class.js +++ b/eslint/tests/lib/rules/no-empty-character-class.js @@ -32,6 +32,7 @@ ruleTester.run("no-empty-character-class", rule, { "var foo = /\\s*:\\s*/gim;", { code: "var foo = /[\\]]/uy;", parserOptions: { ecmaVersion: 6 } }, { code: "var foo = /[\\]]/s;", parserOptions: { ecmaVersion: 2018 } }, + { code: "var foo = /[\\]]/d;", parserOptions: { ecmaVersion: 2022 } }, "var foo = /\\[]/" ], invalid: [ @@ -41,6 +42,7 @@ ruleTester.run("no-empty-character-class", rule, { { code: "if (/^abc[]/.test(foo)) {}", errors: [{ messageId: "unexpected", type: "Literal" }] }, { code: "var foo = /[]]/;", errors: [{ messageId: "unexpected", type: "Literal" }] }, { code: "var foo = /\\[[]/;", errors: [{ messageId: "unexpected", type: "Literal" }] }, - { code: "var foo = /\\[\\[\\]a-z[]/;", errors: [{ messageId: "unexpected", type: "Literal" }] } + { code: "var foo = /\\[\\[\\]a-z[]/;", errors: [{ messageId: "unexpected", type: "Literal" }] }, + { code: "var foo = /[]]/d;", parserOptions: { ecmaVersion: 2022 }, errors: [{ messageId: "unexpected", type: "Literal" }] } ] }); diff --git a/eslint/tests/lib/rules/no-eval.js b/eslint/tests/lib/rules/no-eval.js index bc8d384..79ba4a1 100644 --- a/eslint/tests/lib/rules/no-eval.js +++ b/eslint/tests/lib/rules/no-eval.js @@ -48,6 +48,9 @@ ruleTester.run("no-eval", rule, { "var obj = {}; obj.foo = function() { this.eval('foo'); }", { code: "class A { foo() { this.eval(); } }", parserOptions: { ecmaVersion: 6 } }, { code: "class A { static foo() { this.eval(); } }", parserOptions: { ecmaVersion: 6 } }, + { code: "class A { field = this.eval(); }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { field = () => this.eval(); }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { static { this.eval(); } }", parserOptions: { ecmaVersion: 2022 } }, // Allows indirect eval { code: "(0, eval)('foo')", options: [{ allowIndirect: true }] }, @@ -123,6 +126,19 @@ ruleTester.run("no-eval", rule, { parserOptions: { ecmaVersion: 2020 }, globals: { window: "readonly" }, errors: [{ messageId: "unexpected" }] + }, + + // Class fields + { + code: "class C { [this.eval('foo')] }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpected" }] + }, + + { + code: "class A { static {} [this.eval()]; }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpected" }] } ] }); diff --git a/eslint/tests/lib/rules/no-extra-boolean-cast.js b/eslint/tests/lib/rules/no-extra-boolean-cast.js index 8dda699..8aecb4c 100644 --- a/eslint/tests/lib/rules/no-extra-boolean-cast.js +++ b/eslint/tests/lib/rules/no-extra-boolean-cast.js @@ -13,7 +13,7 @@ const rule = require("../../../lib/rules/no-extra-boolean-cast"), { RuleTester } = require("../../../lib/rule-tester"); //------------------------------------------------------------------------------ -// Helpers +// Tests //------------------------------------------------------------------------------ const ruleTester = new RuleTester(); diff --git a/eslint/tests/lib/rules/no-extra-parens.js b/eslint/tests/lib/rules/no-extra-parens.js index a14ecec..cb488c8 100644 --- a/eslint/tests/lib/rules/no-extra-parens.js +++ b/eslint/tests/lib/rules/no-extra-parens.js @@ -12,6 +12,10 @@ const rule = require("../../../lib/rules/no-extra-parens"), { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + /** * Create error message object for failure cases * @param {string} code source code @@ -42,9 +46,13 @@ function invalid(code, output, type, line, config) { return result; } +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester({ parserOptions: { - ecmaVersion: 2021, + ecmaVersion: 2022, ecmaFeatures: { jsx: true } @@ -182,6 +190,25 @@ ruleTester.run("no-extra-parens", rule, { "class foo { a(){} [b](){} c(){} [(d,e)](){} }", "class foo { [(a,b)](){} c(){} [d](){} e(){} }", "const foo = class { constructor(){} a(){} get b(){} set b(bar){} get c(){} set d(baz){} static e(){} }", + "class foo { x; }", + "class foo { static x; }", + "class foo { x = 1; }", + "class foo { static x = 1; }", + "class foo { #x; }", + "class foo { static #x; }", + "class foo { static #x = 1; }", + "class foo { #x(){} get #y() {} set #y(value) {} static #z(){} static get #q() {} static set #q(value) {} }", + "const foo = class { #x(){} get #y() {} set #y(value) {} static #z(){} static get #q() {} static set #q(value) {} }", + "class foo { [(x, y)]; }", + "class foo { static [(x, y)]; }", + "class foo { [(x, y)] = 1; }", + "class foo { static [(x, y)] = 1; }", + "class foo { x = (y, z); }", + "class foo { static x = (y, z); }", + "class foo { #x = (y, z); }", + "class foo { static #x = (y, z); }", + "class foo { [(1, 2)] = (3, 4) }", + "const foo = class { [(1, 2)] = (3, 4) }", // ExpressionStatement restricted productions "({});", @@ -771,6 +798,26 @@ ruleTester.run("no-extra-parens", rule, { invalid("class foo { [(a,b)](){} [(c+d)](){} }", "class foo { [(a,b)](){} [c+d](){} }", "BinaryExpression"), invalid("class foo { [a+(b*c)](){} }", "class foo { [a+b*c](){} }", "BinaryExpression"), invalid("const foo = class { [(a)](){} }", "const foo = class { [a](){} }", "Identifier"), + invalid("class foo { [(x)]; }", "class foo { [x]; }", "Identifier"), + invalid("class foo { static [(x)]; }", "class foo { static [x]; }", "Identifier"), + invalid("class foo { [(x)] = 1; }", "class foo { [x] = 1; }", "Identifier"), + invalid("class foo { static [(x)] = 1; }", "class foo { static [x] = 1; }", "Identifier"), + invalid("const foo = class { [(x)]; }", "const foo = class { [x]; }", "Identifier"), + invalid("class foo { [(x = y)]; }", "class foo { [x = y]; }", "AssignmentExpression"), + invalid("class foo { [(x + y)]; }", "class foo { [x + y]; }", "BinaryExpression"), + invalid("class foo { [(x ? y : z)]; }", "class foo { [x ? y : z]; }", "ConditionalExpression"), + invalid("class foo { [((x, y))]; }", "class foo { [(x, y)]; }", "SequenceExpression"), + invalid("class foo { x = (y); }", "class foo { x = y; }", "Identifier"), + invalid("class foo { static x = (y); }", "class foo { static x = y; }", "Identifier"), + invalid("class foo { #x = (y); }", "class foo { #x = y; }", "Identifier"), + invalid("class foo { static #x = (y); }", "class foo { static #x = y; }", "Identifier"), + invalid("const foo = class { x = (y); }", "const foo = class { x = y; }", "Identifier"), + invalid("class foo { x = (() => {}); }", "class foo { x = () => {}; }", "ArrowFunctionExpression"), + invalid("class foo { x = (y + z); }", "class foo { x = y + z; }", "BinaryExpression"), + invalid("class foo { x = (y ? z : q); }", "class foo { x = y ? z : q; }", "ConditionalExpression"), + invalid("class foo { x = ((y, z)); }", "class foo { x = (y, z); }", "SequenceExpression"), + + // invalid( "var foo = (function*() { if ((yield foo())) { return; } }())", "var foo = (function*() { if (yield foo()) { return; } }())", diff --git a/eslint/tests/lib/rules/no-extra-semi.js b/eslint/tests/lib/rules/no-extra-semi.js index 913290a..4f19fb7 100644 --- a/eslint/tests/lib/rules/no-extra-semi.js +++ b/eslint/tests/lib/rules/no-extra-semi.js @@ -38,6 +38,9 @@ ruleTester.run("no-extra-semi", rule, { { code: "class A { a() { this; } }", parserOptions: { ecmaVersion: 6 } }, { code: "var A = class { a() { this; } };", parserOptions: { ecmaVersion: 6 } }, { code: "class A { } a;", parserOptions: { ecmaVersion: 6 } }, + { code: "class A { field; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { field = 0; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A { static { foo; } }", parserOptions: { ecmaVersion: 2022 } }, // modules { code: "export const x = 42;", parserOptions: { ecmaVersion: 6, sourceType: "module" } }, @@ -110,6 +113,18 @@ ruleTester.run("no-extra-semi", rule, { output: "with(foo){}", errors: [{ messageId: "unexpected", type: "EmptyStatement" }] }, + { + code: "class A { static { ; } }", + output: "class A { static { } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpected", type: "EmptyStatement", column: 20 }] + }, + { + code: "class A { static { a;; } }", + output: "class A { static { a; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpected", type: "EmptyStatement", column: 22 }] + }, // Class body. { @@ -157,6 +172,24 @@ ruleTester.run("no-extra-semi", rule, { output: "class A { a() {} get b() {} }", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", type: "Punctuator", column: 17 }] + }, + { + code: "class A { field;; }", + output: "class A { field; }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpected", type: "Punctuator", column: 17 }] + }, + { + code: "class A { static {}; }", + output: "class A { static {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpected", type: "Punctuator", column: 20 }] + }, + { + code: "class A { static { a; }; foo(){} }", + output: "class A { static { a; } foo(){} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpected", type: "Punctuator", column: 24 }] } ] }); diff --git a/eslint/tests/lib/rules/no-fallthrough.js b/eslint/tests/lib/rules/no-fallthrough.js index e154c31..0b98d32 100644 --- a/eslint/tests/lib/rules/no-fallthrough.js +++ b/eslint/tests/lib/rules/no-fallthrough.js @@ -30,6 +30,14 @@ ruleTester.run("no-fallthrough", rule, { "switch(foo) { case 0: a(); /* fall through */ case 1: b(); }", "switch(foo) { case 0: a(); /* fallthrough */ case 1: b(); }", "switch(foo) { case 0: a(); /* FALLS THROUGH */ case 1: b(); }", + "switch(foo) { case 0: { a(); /* falls through */ } case 1: b(); }", + "switch(foo) { case 0: { a()\n /* falls through */ } case 1: b(); }", + "switch(foo) { case 0: { a(); /* fall through */ } case 1: b(); }", + "switch(foo) { case 0: { a(); /* fallthrough */ } case 1: b(); }", + "switch(foo) { case 0: { a(); /* FALLS THROUGH */ } case 1: b(); }", + "switch(foo) { case 0: { a(); } /* falls through */ case 1: b(); }", + "switch(foo) { case 0: { a(); /* falls through */ } /* comment */ case 1: b(); }", + "switch(foo) { case 0: { /* falls through */ } case 1: b(); }", "function foo() { switch(foo) { case 0: a(); return; case 1: b(); }; }", "switch(foo) { case 0: a(); throw 'foo'; case 1: b(); }", "while (a) { switch(foo) { case 0: a(); continue; case 1: b(); } }", @@ -133,6 +141,30 @@ ruleTester.run("no-fallthrough", rule, { code: "switch(foo) { case 0:\n\n default: b() }", errors: errorsDefault }, + { + code: "switch(foo) { case 0: {} default: b() }", + errors: errorsDefault + }, + { + code: "switch(foo) { case 0: a(); { /* falls through */ } default: b() }", + errors: errorsDefault + }, + { + code: "switch(foo) { case 0: { /* falls through */ } a(); default: b() }", + errors: errorsDefault + }, + { + code: "switch(foo) { case 0: if (a) { /* falls through */ } default: b() }", + errors: errorsDefault + }, + { + code: "switch(foo) { case 0: { { /* falls through */ } } default: b() }", + errors: errorsDefault + }, + { + code: "switch(foo) { case 0: { /* comment */ } default: b() }", + errors: errorsDefault + }, { code: "switch(foo) { case 0:\n // comment\n default: b() }", errors: errorsDefault @@ -168,6 +200,20 @@ ruleTester.run("no-fallthrough", rule, { column: 1 } ] + }, + { + code: "switch(foo) { case 0: { a();\n/* no break */\n/* todo: fix readability */ }\ndefault: b() }", + options: [{ + commentPattern: "no break" + }], + errors: [ + { + messageId: "default", + type: "SwitchCase", + line: 4, + column: 1 + } + ] } ] }); diff --git a/eslint/tests/lib/rules/no-inner-declarations.js b/eslint/tests/lib/rules/no-inner-declarations.js index a9f70fd..5fcb2f3 100644 --- a/eslint/tests/lib/rules/no-inner-declarations.js +++ b/eslint/tests/lib/rules/no-inner-declarations.js @@ -73,8 +73,27 @@ ruleTester.run("no-inner-declarations", rule, { { code: "module.exports = function foo(){}", options: ["both"] + }, + { + code: "class C { method() { function foo() {} } }", + options: ["both"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { method() { var x; } }", + options: ["both"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { function foo() {} } }", + options: ["both"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { var x; } }", + options: ["both"], + parserOptions: { ecmaVersion: 2022 } } - ], // Examples of code that should trigger the rule @@ -271,7 +290,54 @@ ruleTester.run("no-inner-declarations", rule, { }, type: "VariableDeclaration" }] + }, { + code: "class C { method() { if(test) { var foo; } } }", + options: ["both"], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "moveDeclToRoot", + data: { + type: "variable", + body: "function body" + }, + type: "VariableDeclaration" + }] + }, { + code: "class C { static { if (test) { function foo() {} } } }", + options: ["both"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "moveDeclToRoot", + data: { + type: "function", + body: "class static block body" + }, + type: "FunctionDeclaration" + }] + }, { + code: "class C { static { if (test) { var foo; } } }", + options: ["both"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "moveDeclToRoot", + data: { + type: "variable", + body: "class static block body" + }, + type: "VariableDeclaration" + }] + }, { + code: "class C { static { if (test) { if (anotherTest) { var foo; } } } }", + options: ["both"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "moveDeclToRoot", + data: { + type: "variable", + body: "class static block body" + }, + type: "VariableDeclaration" + }] } - ] }); diff --git a/eslint/tests/lib/rules/no-invalid-regexp.js b/eslint/tests/lib/rules/no-invalid-regexp.js index 6484db8..a34752d 100644 --- a/eslint/tests/lib/rules/no-invalid-regexp.js +++ b/eslint/tests/lib/rules/no-invalid-regexp.js @@ -12,6 +12,10 @@ const rule = require("../../../lib/rules/no-invalid-regexp"), { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester(); ruleTester.run("no-invalid-regexp", rule, { @@ -59,6 +63,9 @@ ruleTester.run("no-invalid-regexp", rule, { "new RegExp('(?<𝒜>.)', 'g');", "new RegExp('\\\\p{Script=Nandinagari}', 'u');", + // ES2022 + "new RegExp('a+(?z)?', 'd')", + // allowConstructorFlags { code: "new RegExp('.', 'g')", diff --git a/eslint/tests/lib/rules/no-invalid-this.js b/eslint/tests/lib/rules/no-invalid-this.js index 3b19d63..62033fd 100644 --- a/eslint/tests/lib/rules/no-invalid-this.js +++ b/eslint/tests/lib/rules/no-invalid-this.js @@ -90,7 +90,6 @@ function extractPatterns(patterns, type) { return [].concat(...patternsList); } - //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ @@ -724,6 +723,8 @@ const patterns = [ valid: [NORMAL], invalid: [USE_STRICT, IMPLIED_STRICT, MODULES] }, + + // Logical assignments { code: "obj.method &&= function () { console.log(this); z(x => console.log(x, this)); }", parserOptions: { ecmaVersion: 2021 }, @@ -741,6 +742,87 @@ const patterns = [ parserOptions: { ecmaVersion: 2021 }, valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], invalid: [] + }, + + // Class fields. + { + code: "class C { field = console.log(this); }", + parserOptions: { ecmaVersion: 2022 }, + valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], + invalid: [] + }, + { + code: "class C { field = z(x => console.log(x, this)); }", + parserOptions: { ecmaVersion: 2022 }, + valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], + invalid: [] + }, + { + code: "class C { field = function () { console.log(this); z(x => console.log(x, this)); }; }", + parserOptions: { ecmaVersion: 2022 }, + valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], + invalid: [] + }, + { + code: "class C { #field = function () { console.log(this); z(x => console.log(x, this)); }; }", + parserOptions: { ecmaVersion: 2022 }, + valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], + invalid: [] + }, + { + code: "class C { [this.foo]; }", + parserOptions: { ecmaVersion: 2022 }, + valid: [NORMAL], // the global this in non-strict mode is OK. + invalid: [USE_STRICT, IMPLIED_STRICT, MODULES], + errors: [{ messageId: "unexpectedThis", type: "ThisExpression" }] + }, + + // Class static blocks + { + code: "class C { static { this.x; } }", + parserOptions: { ecmaVersion: 2022 }, + valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], + invalid: [] + }, + { + code: "class C { static { () => { this.x; } } }", + parserOptions: { ecmaVersion: 2022 }, + valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], + invalid: [] + }, + { + code: "class C { static { class D { [this.x]; } } }", + parserOptions: { ecmaVersion: 2022 }, + valid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], + invalid: [] + }, + { + code: "class C { static { function foo() { this.x; } } }", + parserOptions: { ecmaVersion: 2022 }, + valid: [], + invalid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], + errors: [{ messageId: "unexpectedThis", type: "ThisExpression" }] + }, + { + code: "class C { static { (function() { this.x; }); } }", + parserOptions: { ecmaVersion: 2022 }, + valid: [], + invalid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], + errors: [{ messageId: "unexpectedThis", type: "ThisExpression" }] + }, + { + code: "class C { static { (function() { this.x; })(); } }", + parserOptions: { ecmaVersion: 2022 }, + valid: [], + invalid: [NORMAL, USE_STRICT, IMPLIED_STRICT, MODULES], + errors: [{ messageId: "unexpectedThis", type: "ThisExpression" }] + }, + { + code: "class C { static {} [this.x]; }", + parserOptions: { ecmaVersion: 2022 }, + valid: [NORMAL], + invalid: [USE_STRICT, IMPLIED_STRICT, MODULES], + errors: [{ messageId: "unexpectedThis", type: "ThisExpression" }] } ]; diff --git a/eslint/tests/lib/rules/no-lone-blocks.js b/eslint/tests/lib/rules/no-lone-blocks.js index 6fa29d2..ab81caf 100644 --- a/eslint/tests/lib/rules/no-lone-blocks.js +++ b/eslint/tests/lib/rules/no-lone-blocks.js @@ -13,7 +13,7 @@ const rule = require("../../../lib/rules/no-lone-blocks"), { RuleTester } = require("../../../lib/rule-tester"); //------------------------------------------------------------------------------ -// Helpers +// Tests //------------------------------------------------------------------------------ const ruleTester = new RuleTester(); @@ -57,7 +57,16 @@ ruleTester.run("no-lone-blocks", rule, { } } `, - { code: "function foo() { { const x = 4 } const x = 3 }", parserOptions: { ecmaVersion: 6 } } + { code: "function foo() { { const x = 4 } const x = 3 }", parserOptions: { ecmaVersion: 6 } }, + + { code: "class C { static {} }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo; } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { if (foo) { block; } } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { lbl: { block; } } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { { let block; } something; } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { something; { const block = 1; } } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { { function block(){} } something; } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { something; { class block {} } } }", parserOptions: { ecmaVersion: 2022 } } ], invalid: [ { @@ -235,6 +244,202 @@ ruleTester.run("no-lone-blocks", rule, { type: "BlockStatement", line: 3 }] + }, + { + code: ` + class C { + static { + if (foo) { + { + let block; + } + } + } + } + `, + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "redundantNestedBlock", + type: "BlockStatement", + line: 5 + }] + }, + { + code: ` + class C { + static { + if (foo) { + { + block; + } + something; + } + } + } + `, + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "redundantNestedBlock", + type: "BlockStatement", + line: 5 + }] + }, + { + code: ` + class C { + static { + { + block; + } + } + } + `, + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "redundantNestedBlock", + type: "BlockStatement", + line: 4 + }] + }, + { + code: ` + class C { + static { + { + let block; + } + } + } + `, + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "redundantNestedBlock", + type: "BlockStatement", + line: 4 + }] + }, + { + code: ` + class C { + static { + { + const block = 1; + } + } + } + `, + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "redundantNestedBlock", + type: "BlockStatement", + line: 4 + }] + }, + { + code: ` + class C { + static { + { + function block() {} + } + } + } + `, + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "redundantNestedBlock", + type: "BlockStatement", + line: 4 + }] + }, + { + code: ` + class C { + static { + { + class block {} + } + } + } + `, + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "redundantNestedBlock", + type: "BlockStatement", + line: 4 + }] + }, + { + code: ` + class C { + static { + { + var block; + } + something; + } + } + `, + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "redundantNestedBlock", + type: "BlockStatement", + line: 4 + }] + }, + { + code: ` + class C { + static { + something; + { + var block; + } + } + } + `, + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "redundantNestedBlock", + type: "BlockStatement", + line: 5 + }] + }, + { + code: ` + class C { + static { + { + block; + } + something; + } + } + `, + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "redundantNestedBlock", + type: "BlockStatement", + line: 4 + }] + }, + { + code: ` + class C { + static { + something; + { + block; + } + } + } + `, + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "redundantNestedBlock", + type: "BlockStatement", + line: 5 + }] } ] }); diff --git a/eslint/tests/lib/rules/no-loss-of-precision.js b/eslint/tests/lib/rules/no-loss-of-precision.js index c690dd7..f268f11 100644 --- a/eslint/tests/lib/rules/no-loss-of-precision.js +++ b/eslint/tests/lib/rules/no-loss-of-precision.js @@ -13,7 +13,7 @@ const rule = require("../../../lib/rules/no-loss-of-precision"), { RuleTester } = require("../../../lib/rule-tester"); //------------------------------------------------------------------------------ -// Helpers +// Tests //------------------------------------------------------------------------------ const ruleTester = new RuleTester(); diff --git a/eslint/tests/lib/rules/no-magic-numbers.js b/eslint/tests/lib/rules/no-magic-numbers.js index bbdf8ca..6afc328 100644 --- a/eslint/tests/lib/rules/no-magic-numbers.js +++ b/eslint/tests/lib/rules/no-magic-numbers.js @@ -13,7 +13,7 @@ const rule = require("../../../lib/rules/no-magic-numbers"), { RuleTester } = require("../../../lib/rule-tester"); //------------------------------------------------------------------------------ -// Helpers +// Tests //------------------------------------------------------------------------------ const ruleTester = new RuleTester(); diff --git a/eslint/tests/lib/rules/no-misleading-character-class.js b/eslint/tests/lib/rules/no-misleading-character-class.js index a7bc04e..a02e5e1 100644 --- a/eslint/tests/lib/rules/no-misleading-character-class.js +++ b/eslint/tests/lib/rules/no-misleading-character-class.js @@ -12,7 +12,7 @@ const rule = require("../../../lib/rules/no-misleading-character-class"), { RuleTester } = require("../../../lib/rule-tester"); //------------------------------------------------------------------------------ -// Helpers +// Tests //------------------------------------------------------------------------------ const ruleTester = new RuleTester({ diff --git a/eslint/tests/lib/rules/no-multi-assign.js b/eslint/tests/lib/rules/no-multi-assign.js index c534f55..7920d99 100644 --- a/eslint/tests/lib/rules/no-multi-assign.js +++ b/eslint/tests/lib/rules/no-multi-assign.js @@ -13,7 +13,7 @@ const rule = require("../../../lib/rules/no-multi-assign"), { RuleTester } = require("../../../lib/rule-tester"); //------------------------------------------------------------------------------ -// Fixtures +// Helpers //------------------------------------------------------------------------------ /** @@ -33,7 +33,6 @@ function errorAt(line, column, type) { }; } - //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ @@ -53,7 +52,11 @@ ruleTester.run("no-mutli-assign", rule, { { code: "export let a, b;", parserOptions: { ecmaVersion: 6, sourceType: "module" } }, { code: "export let a,\n b = 0;", parserOptions: { ecmaVersion: 6, sourceType: "module" } }, { code: "const x = {};const y = {};x.one = y.one = 1;", options: [{ ignoreNonDeclaration: true }], parserOptions: { ecmaVersion: 6 } }, - { code: "let a, b;a = b = 1", options: [{ ignoreNonDeclaration: true }], parserOptions: { ecmaVersion: 6 } } + { code: "let a, b;a = b = 1", options: [{ ignoreNonDeclaration: true }], parserOptions: { ecmaVersion: 6 } }, + { + code: "class C { [foo = 0] = 0 }", + parserOptions: { ecmaVersion: 2022 } + } ], invalid: [ @@ -172,6 +175,21 @@ ruleTester.run("no-mutli-assign", rule, { errors: [ errorAt(1, 11, "AssignmentExpression") ] + }, + { + code: "class C { field = foo = 0 }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + errorAt(1, 19, "AssignmentExpression") + ] + }, + { + code: "class C { field = foo = 0 }", + options: [{ ignoreNonDeclaration: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + errorAt(1, 19, "AssignmentExpression") + ] } ] }); diff --git a/eslint/tests/lib/rules/no-multiple-empty-lines.js b/eslint/tests/lib/rules/no-multiple-empty-lines.js index 5291fbb..bcec4e4 100644 --- a/eslint/tests/lib/rules/no-multiple-empty-lines.js +++ b/eslint/tests/lib/rules/no-multiple-empty-lines.js @@ -12,11 +12,9 @@ const rule = require("../../../lib/rules/no-multiple-empty-lines"), { RuleTester } = require("../../../lib/rule-tester"); //------------------------------------------------------------------------------ -// Tests +// Helpers //------------------------------------------------------------------------------ -const ruleTester = new RuleTester(); - /** * Creates the expected error message object for the specified number of lines * @param {lines} lines The number of lines expected. @@ -69,6 +67,11 @@ function getExpectedErrorBOF(lines) { }; } +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester(); ruleTester.run("no-multiple-empty-lines", rule, { diff --git a/eslint/tests/lib/rules/no-new-func.js b/eslint/tests/lib/rules/no-new-func.js index aa05420..1ac02d9 100644 --- a/eslint/tests/lib/rules/no-new-func.js +++ b/eslint/tests/lib/rules/no-new-func.js @@ -38,7 +38,11 @@ ruleTester.run("no-new-func", rule, { "var fn = function () { function Function() {}; Function() }", "var x = function Function() { Function(); }", "call(Function)", - "new Class(Function)" + "new Class(Function)", + "foo[Function]()", + "foo(Function.bind)", + "Function.toString()", + "Function[call]()" ], invalid: [ { @@ -55,6 +59,49 @@ ruleTester.run("no-new-func", rule, { type: "CallExpression" }] }, + { + code: "var a = Function.call(null, \"b\", \"c\", \"return b+c\");", + errors: [{ + messageId: "noFunctionConstructor", + type: "CallExpression" + }] + }, + { + code: "var a = Function.apply(null, [\"b\", \"c\", \"return b+c\"]);", + errors: [{ + messageId: "noFunctionConstructor", + type: "CallExpression" + }] + }, + { + code: "var a = Function.bind(null, \"b\", \"c\", \"return b+c\")();", + errors: [{ + messageId: "noFunctionConstructor", + type: "CallExpression" + }] + }, + { + code: "var a = Function.bind(null, \"b\", \"c\", \"return b+c\");", + errors: [{ + messageId: "noFunctionConstructor", + type: "CallExpression" + }] + }, + { + code: "var a = Function[\"call\"](null, \"b\", \"c\", \"return b+c\");", + errors: [{ + messageId: "noFunctionConstructor", + type: "CallExpression" + }] + }, + { + code: "var a = (Function?.call)(null, \"b\", \"c\", \"return b+c\");", + parserOptions: { ecmaVersion: 2021 }, + errors: [{ + messageId: "noFunctionConstructor", + type: "CallExpression" + }] + }, { code: "const fn = () => { class Function {} }; new Function('', '')", parserOptions: { diff --git a/eslint/tests/lib/rules/no-proto.js b/eslint/tests/lib/rules/no-proto.js index 3e0e7e0..c9b7135 100644 --- a/eslint/tests/lib/rules/no-proto.js +++ b/eslint/tests/lib/rules/no-proto.js @@ -23,7 +23,8 @@ ruleTester.run("no-proto", rule, { "var a = test[__proto__];", "var __proto__ = null;", { code: "foo[`__proto`] = null;", parserOptions: { ecmaVersion: 6 } }, - { code: "foo[`__proto__\n`] = null;", parserOptions: { ecmaVersion: 6 } } + { code: "foo[`__proto__\n`] = null;", parserOptions: { ecmaVersion: 6 } }, + { code: "class C { #__proto__; foo() { this.#__proto__; } }", parserOptions: { ecmaVersion: 2022 } } ], invalid: [ { code: "var a = test.__proto__;", errors: [{ messageId: "unexpectedProto", type: "MemberExpression" }] }, diff --git a/eslint/tests/lib/rules/no-prototype-builtins.js b/eslint/tests/lib/rules/no-prototype-builtins.js index a65b54d..6152e8a 100644 --- a/eslint/tests/lib/rules/no-prototype-builtins.js +++ b/eslint/tests/lib/rules/no-prototype-builtins.js @@ -15,6 +15,7 @@ const rule = require("../../../lib/rules/no-prototype-builtins"), //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ + const ruleTester = new RuleTester(); ruleTester.run("no-prototype-builtins", rule, { @@ -43,6 +44,7 @@ ruleTester.run("no-prototype-builtins", rule, { { code: "foo?.['propertyIsEnumerabl']('bar')", parserOptions: { ecmaVersion: 2020 } }, "foo[1]('bar')", "foo[null]('bar')", + { code: "class C { #hasOwnProperty; foo() { obj.#hasOwnProperty('bar'); } }", parserOptions: { ecmaVersion: 2022 } }, // out of scope for this rule "foo['hasOwn' + 'Property']('bar')", diff --git a/eslint/tests/lib/rules/no-redeclare.js b/eslint/tests/lib/rules/no-redeclare.js index f89d685..bbbe580 100644 --- a/eslint/tests/lib/rules/no-redeclare.js +++ b/eslint/tests/lib/rules/no-redeclare.js @@ -28,6 +28,72 @@ ruleTester.run("no-redeclare", rule, { ecmaVersion: 6 } }, + { + code: "var a; class C { static { var a; } }", + parserOptions: { + ecmaVersion: 2022 + } + }, + { + code: "class C { static { var a; } } var a; ", + parserOptions: { + ecmaVersion: 2022 + } + }, + { + code: "function a(){} class C { static { var a; } }", + parserOptions: { + ecmaVersion: 2022 + } + }, + { + code: "var a; class C { static { function a(){} } }", + parserOptions: { + ecmaVersion: 2022 + } + }, + { + code: "class C { static { var a; } static { var a; } }", + parserOptions: { + ecmaVersion: 2022 + } + }, + { + code: "class C { static { function a(){} } static { function a(){} } }", + parserOptions: { + ecmaVersion: 2022 + } + }, + { + code: "class C { static { var a; { function a(){} } } }", + parserOptions: { + ecmaVersion: 2022 + } + }, + { + code: "class C { static { function a(){}; { function a(){} } } }", + parserOptions: { + ecmaVersion: 2022 + } + }, + { + code: "class C { static { var a; { let a; } } }", + parserOptions: { + ecmaVersion: 2022 + } + }, + { + code: "class C { static { let a; { let a; } } }", + parserOptions: { + ecmaVersion: 2022 + } + }, + { + code: "class C { static { { let a; } { let a; } } }", + parserOptions: { + ecmaVersion: 2022 + } + }, { code: "var Object = 0;", options: [{ builtinGlobals: false }] }, { code: "var Object = 0;", options: [{ builtinGlobals: true }], parserOptions: { ecmaVersion: 6, sourceType: "module" } }, { code: "var Object = 0;", options: [{ builtinGlobals: true }], parserOptions: { ecmaFeatures: { globalReturn: true } } }, @@ -80,6 +146,37 @@ ruleTester.run("no-redeclare", rule, { { code: "var a = 3; var a = 10; var a = 15;", errors: [{ message: "'a' is already defined.", type: "Identifier" }, { message: "'a' is already defined.", type: "Identifier" }] }, { code: "var a; var a;", parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ message: "'a' is already defined.", type: "Identifier" }] }, { code: "export var a; var a;", parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ message: "'a' is already defined.", type: "Identifier" }] }, + + // `var` redeclaration in class static blocks. Redeclaration of functions is not allowed in class static blocks. + { + code: "class C { static { var a; var a; } }", + parserOptions: { + ecmaVersion: 2022 + }, + errors: [{ message: "'a' is already defined.", type: "Identifier" }] + }, + { + code: "class C { static { var a; { var a; } } }", + parserOptions: { + ecmaVersion: 2022 + }, + errors: [{ message: "'a' is already defined.", type: "Identifier" }] + }, + { + code: "class C { static { { var a; } var a; } }", + parserOptions: { + ecmaVersion: 2022 + }, + errors: [{ message: "'a' is already defined.", type: "Identifier" }] + }, + { + code: "class C { static { { var a; } { var a; } } }", + parserOptions: { + ecmaVersion: 2022 + }, + errors: [{ message: "'a' is already defined.", type: "Identifier" }] + }, + { code: "var Object = 0;", options: [{ builtinGlobals: true }], diff --git a/eslint/tests/lib/rules/no-regex-spaces.js b/eslint/tests/lib/rules/no-regex-spaces.js index fdcd1ce..8995229 100644 --- a/eslint/tests/lib/rules/no-regex-spaces.js +++ b/eslint/tests/lib/rules/no-regex-spaces.js @@ -12,6 +12,10 @@ const rule = require("../../../lib/rules/no-regex-spaces"), { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester(); ruleTester.run("no-regex-spaces", rule, { diff --git a/eslint/tests/lib/rules/no-restricted-imports.js b/eslint/tests/lib/rules/no-restricted-imports.js index f54d4df..c86e67e 100644 --- a/eslint/tests/lib/rules/no-restricted-imports.js +++ b/eslint/tests/lib/rules/no-restricted-imports.js @@ -29,6 +29,19 @@ ruleTester.run("no-restricted-imports", rule, { { code: "import \"foo/bar\";", options: ["foo"] }, { code: "import withPaths from \"foo/bar\";", options: [{ paths: ["foo", "bar"] }] }, { code: "import withPatterns from \"foo/bar\";", options: [{ patterns: ["foo/c*"] }] }, + { code: "import foo from 'foo';", options: ["../foo"] }, + { code: "import foo from 'foo';", options: [{ paths: ["../foo"] }] }, + { code: "import foo from 'foo';", options: [{ patterns: ["../foo"] }] }, + { code: "import foo from 'foo';", options: ["/foo"] }, + { code: "import foo from 'foo';", options: [{ paths: ["/foo"] }] }, + "import relative from '../foo';", + { code: "import relative from '../foo';", options: ["../notFoo"] }, + { code: "import relativeWithPaths from '../foo';", options: [{ paths: ["../notFoo"] }] }, + { code: "import relativeWithPatterns from '../foo';", options: [{ patterns: ["notFoo"] }] }, + "import absolute from '/foo';", + { code: "import absolute from '/foo';", options: ["/notFoo"] }, + { code: "import absoluteWithPaths from '/foo';", options: [{ paths: ["/notFoo"] }] }, + { code: "import absoluteWithPatterns from '/foo';", options: [{ patterns: ["notFoo"] }] }, { code: "import withPatternsAndPaths from \"foo/bar\";", options: [{ paths: ["foo"], patterns: ["foo/c*"] }] @@ -815,6 +828,72 @@ ruleTester.run("no-restricted-imports", rule, { column: 15, endColumn: 29 }] + }, + { + code: "import relative from '../foo';", + options: ["../foo"], + errors: [{ + message: "'../foo' import is restricted from being used.", + type: "ImportDeclaration", + line: 1, + column: 1, + endColumn: 31 + }] + }, + { + code: "import relativeWithPaths from '../foo';", + options: [{ paths: ["../foo"] }], + errors: [{ + message: "'../foo' import is restricted from being used.", + type: "ImportDeclaration", + line: 1, + column: 1, + endColumn: 40 + }] + }, + { + code: "import relativeWithPatterns from '../foo';", + options: [{ patterns: ["../foo"] }], + errors: [{ + message: "'../foo' import is restricted from being used by a pattern.", + type: "ImportDeclaration", + line: 1, + column: 1, + endColumn: 43 + }] + }, + { + code: "import absolute from '/foo';", + options: ["/foo"], + errors: [{ + message: "'/foo' import is restricted from being used.", + type: "ImportDeclaration", + line: 1, + column: 1, + endColumn: 29 + }] + }, + { + code: "import absoluteWithPaths from '/foo';", + options: [{ paths: ["/foo"] }], + errors: [{ + message: "'/foo' import is restricted from being used.", + type: "ImportDeclaration", + line: 1, + column: 1, + endColumn: 38 + }] + }, + { + code: "import absoluteWithPatterns from '/foo';", + options: [{ patterns: ["foo"] }], + errors: [{ + message: "'/foo' import is restricted from being used by a pattern.", + type: "ImportDeclaration", + line: 1, + column: 1, + endColumn: 41 + }] } ] }); diff --git a/eslint/tests/lib/rules/no-restricted-modules.js b/eslint/tests/lib/rules/no-restricted-modules.js index 08cc7c0..5e89a6a 100644 --- a/eslint/tests/lib/rules/no-restricted-modules.js +++ b/eslint/tests/lib/rules/no-restricted-modules.js @@ -32,7 +32,20 @@ ruleTester.run("no-restricted-modules", rule, { { code: "var withPatternsAndPaths = require(\"foo/bar\");", options: [{ paths: ["foo"], patterns: ["foo/c*"] }] }, { code: "var withGitignores = require(\"foo/bar\");", options: [{ paths: ["foo"], patterns: ["foo/*", "!foo/bar"] }] }, { code: "require(`fs`)", options: ["crypto"], parserOptions: { ecmaVersion: 6 } }, - { code: "require(`foo${bar}`)", options: ["foo"], parserOptions: { ecmaVersion: 6 } } + { code: "require(`foo${bar}`)", options: ["foo"], parserOptions: { ecmaVersion: 6 } }, + { code: "var foo = require('foo');", options: ["../foo"] }, + { code: "var foo = require('foo');", options: [{ paths: ["../foo"] }] }, + { code: "var foo = require('foo');", options: [{ patterns: ["../foo"] }] }, + { code: "var foo = require('foo');", options: ["/foo"] }, + { code: "var foo = require('foo');", options: [{ paths: ["/foo"] }] }, + "var relative = require('../foo');", + { code: "var relative = require('../foo');", options: ["../notFoo"] }, + { code: "var relativeWithPaths = require('../foo');", options: [{ paths: ["../notFoo"] }] }, + { code: "var relativeWithPatterns = require('../foo');", options: [{ patterns: ["notFoo"] }] }, + "var absolute = require('/foo');", + { code: "var absolute = require('/foo');", options: ["/notFoo"] }, + { code: "var absoluteWithPaths = require('/foo');", options: [{ paths: ["/notFoo"] }] }, + { code: "var absoluteWithPatterns = require('/foo');", options: [{ patterns: ["notFoo"] }] } ], invalid: [{ code: "require(\"fs\")", @@ -111,5 +124,71 @@ ruleTester.run("no-restricted-modules", rule, { options: ["crypto"], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "defaultMessage", data: { name: "crypto" }, type: "CallExpression" }] + }, + { + code: "var relative = require('../foo');", + options: ["../foo"], + errors: [{ + message: "'../foo' module is restricted from being used.", + type: "CallExpression", + line: 1, + column: 16, + endColumn: 33 + }] + }, + { + code: "var relativeWithPaths = require('../foo');", + options: [{ paths: ["../foo"] }], + errors: [{ + message: "'../foo' module is restricted from being used.", + type: "CallExpression", + line: 1, + column: 25, + endColumn: 42 + }] + }, + { + code: "var relativeWithPatterns = require('../foo');", + options: [{ patterns: ["../foo"] }], + errors: [{ + message: "'../foo' module is restricted from being used by a pattern.", + type: "CallExpression", + line: 1, + column: 28, + endColumn: 45 + }] + }, + { + code: "var absolute = require('/foo');", + options: ["/foo"], + errors: [{ + message: "'/foo' module is restricted from being used.", + type: "CallExpression", + line: 1, + column: 16, + endColumn: 31 + }] + }, + { + code: "var absoluteWithPaths = require('/foo');", + options: [{ paths: ["/foo"] }], + errors: [{ + message: "'/foo' module is restricted from being used.", + type: "CallExpression", + line: 1, + column: 25, + endColumn: 40 + }] + }, + { + code: "var absoluteWithPatterns = require('/foo');", + options: [{ patterns: ["foo"] }], + errors: [{ + message: "'/foo' module is restricted from being used by a pattern.", + type: "CallExpression", + line: 1, + column: 28, + endColumn: 43 + }] }] }); diff --git a/eslint/tests/lib/rules/no-restricted-properties.js b/eslint/tests/lib/rules/no-restricted-properties.js index c9fb2b7..7c557bc 100644 --- a/eslint/tests/lib/rules/no-restricted-properties.js +++ b/eslint/tests/lib/rules/no-restricted-properties.js @@ -169,6 +169,10 @@ ruleTester.run("no-restricted-properties", rule, { code: "function qux([, bar] = foo) {}", options: [{ object: "foo", property: "1" }], parserOptions: { ecmaVersion: 6 } + }, { + code: "class C { #foo; foo() { this.#foo; } }", + options: [{ property: "#foo" }], + parserOptions: { ecmaVersion: 2022 } } ], @@ -530,6 +534,18 @@ ruleTester.run("no-restricted-properties", rule, { }, type: "ObjectPattern" }] + }, { + code: "obj['#foo']", + options: [{ property: "#foo" }], + errors: [{ + messageId: "restrictedProperty", + data: { + objectName: "", + propertyName: "#foo", + message: "" + }, + type: "MemberExpression" + }] } ] }); diff --git a/eslint/tests/lib/rules/no-self-assign.js b/eslint/tests/lib/rules/no-self-assign.js index 5a9bb6f..04aa2de 100644 --- a/eslint/tests/lib/rules/no-self-assign.js +++ b/eslint/tests/lib/rules/no-self-assign.js @@ -79,6 +79,14 @@ ruleTester.run("no-self-assign", rule, { { code: "this.x = this.x", options: [{ props: false }] + }, + { + code: "class C { #field; foo() { this['#field'] = this.#field; } }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #field; foo() { this.#field = this['#field']; } }", + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -147,6 +155,18 @@ ruleTester.run("no-self-assign", rule, { code: "a.b = a?.b", parserOptions: { ecmaVersion: 2020 }, errors: [{ messageId: "selfAssignment", data: { name: "a?.b" } }] + }, + + // Private members + { + code: "class C { #field; foo() { this.#field = this.#field; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "selfAssignment", data: { name: "this.#field" } }] + }, + { + code: "class C { #field; foo() { [this.#field] = [this.#field]; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "selfAssignment", data: { name: "this.#field" } }] } ] }); diff --git a/eslint/tests/lib/rules/no-self-compare.js b/eslint/tests/lib/rules/no-self-compare.js index 85bc1e2..ad4d30a 100644 --- a/eslint/tests/lib/rules/no-self-compare.js +++ b/eslint/tests/lib/rules/no-self-compare.js @@ -23,7 +23,15 @@ ruleTester.run("no-self-compare", rule, { "if (x === y) { }", "if (1 === 2) { }", "y=x*x", - "foo.bar.baz === foo.bar.qux" + "foo.bar.baz === foo.bar.qux", + { + code: "class C { #field; foo() { this.#field === this['#field']; } }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #field; foo() { this['#field'] === this.#field; } }", + parserOptions: { ecmaVersion: 2022 } + } ], invalid: [ { code: "if (x === x) { }", errors: [{ messageId: "comparingToSelf", type: "BinaryExpression" }] }, @@ -39,6 +47,11 @@ ruleTester.run("no-self-compare", rule, { { code: "x < x", errors: [{ messageId: "comparingToSelf", type: "BinaryExpression" }] }, { code: "x >= x", errors: [{ messageId: "comparingToSelf", type: "BinaryExpression" }] }, { code: "x <= x", errors: [{ messageId: "comparingToSelf", type: "BinaryExpression" }] }, - { code: "foo.bar().baz.qux >= foo.bar ().baz .qux", errors: [{ messageId: "comparingToSelf", type: "BinaryExpression" }] } + { code: "foo.bar().baz.qux >= foo.bar ().baz .qux", errors: [{ messageId: "comparingToSelf", type: "BinaryExpression" }] }, + { + code: "class C { #field; foo() { this.#field === this.#field; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "comparingToSelf", type: "BinaryExpression" }] + } ] }); diff --git a/eslint/tests/lib/rules/no-setter-return.js b/eslint/tests/lib/rules/no-setter-return.js index 0c64e8b..ab5b196 100644 --- a/eslint/tests/lib/rules/no-setter-return.js +++ b/eslint/tests/lib/rules/no-setter-return.js @@ -39,7 +39,7 @@ function error(column, type = "ReturnStatement") { // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); ruleTester.run("no-setter-return", rule, { valid: [ @@ -114,6 +114,7 @@ ruleTester.run("no-setter-return", rule, { "class A { static set(val) { return 1; } }", "({ set: set = function set(val) { return 1; } } = {})", "({ set: set = (val) => 1 } = {})", + "class C { set; foo() { return 1; } }", // not returning from the setter "({ set foo(val) { function foo(val) { return 1; } } })", diff --git a/eslint/tests/lib/rules/no-shadow-restricted-names.js b/eslint/tests/lib/rules/no-shadow-restricted-names.js index da2df0f..868cd82 100644 --- a/eslint/tests/lib/rules/no-shadow-restricted-names.js +++ b/eslint/tests/lib/rules/no-shadow-restricted-names.js @@ -12,6 +12,10 @@ const rule = require("../../../lib/rules/no-shadow-restricted-names"), { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester(); ruleTester.run("no-shadow-restricted-names", rule, { diff --git a/eslint/tests/lib/rules/no-shadow.js b/eslint/tests/lib/rules/no-shadow.js index dc2cc63..13c4c11 100644 --- a/eslint/tests/lib/rules/no-shadow.js +++ b/eslint/tests/lib/rules/no-shadow.js @@ -57,7 +57,13 @@ ruleTester.run("no-shadow", rule, { { code: "function foo() { var top = 0; }", env: { browser: true } }, { code: "var Object = 0;", options: [{ builtinGlobals: true }] }, { code: "var top = 0;", options: [{ builtinGlobals: true }], env: { browser: true } }, - { code: "function foo(cb) { (function (cb) { cb(42); })(cb); }", options: [{ allow: ["cb"] }] } + { code: "function foo(cb) { (function (cb) { cb(42); })(cb); }", options: [{ allow: ["cb"] }] }, + { code: "class C { foo; foo() { let foo; } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { var x; } static { var x; } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { let x; } static { let x; } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { var x; { var x; /* redeclaration */ } } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { { var x; } { var x; /* redeclaration */ } } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { { let x; } { let x; } } }", parserOptions: { ecmaVersion: 2022 } } ], invalid: [ { @@ -715,6 +721,144 @@ ruleTester.run("no-shadow", rule, { line: 1, column: 31 }] + }, + { + code: "class C { static { let a; { let a; } } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 24 + }, + type: "Identifier", + line: 1, + column: 33 + }] + }, + { + code: "class C { static { var C; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "C", + shadowedLine: 1, + shadowedColumn: 7 + }, + type: "Identifier", + line: 1, + column: 24 + }] + }, + { + code: "class C { static { let C; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "C", + shadowedLine: 1, + shadowedColumn: 7 + }, + type: "Identifier", + line: 1, + column: 24 + }] + }, + { + code: "var a; class C { static { var a; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 5 + }, + type: "Identifier", + line: 1, + column: 31 + }] + }, + { + code: "class C { static { var a; } } var a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 35 + }, + type: "Identifier", + line: 1, + column: 24 + }] + }, + { + code: "class C { static { let a; } } let a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 35 + }, + type: "Identifier", + line: 1, + column: 24 + }] + }, + { + code: "class C { static { var a; } } let a;", + options: [{ hoist: "all" }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 35 + }, + type: "Identifier", + line: 1, + column: 24 + }] + }, + { + code: "class C { static { var a; class D { static { var a; } } } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 24 + }, + type: "Identifier", + line: 1, + column: 50 + }] + }, + { + code: "class C { static { let a; class D { static { let a; } } } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noShadow", + data: { + name: "a", + shadowedLine: 1, + shadowedColumn: 24 + }, + type: "Identifier", + line: 1, + column: 50 + }] } ] }); diff --git a/eslint/tests/lib/rules/no-this-before-super.js b/eslint/tests/lib/rules/no-this-before-super.js index 7a947c0..7b9a8a4 100644 --- a/eslint/tests/lib/rules/no-this-before-super.js +++ b/eslint/tests/lib/rules/no-this-before-super.js @@ -16,7 +16,7 @@ const { RuleTester } = require("../../../lib/rule-tester"); // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); ruleTester.run("no-this-before-super", rule, { valid: [ @@ -97,7 +97,13 @@ ruleTester.run("no-this-before-super", rule, { } } } - ` + `, + + // Class field initializers are always evaluated after `super()`. + "class C { field = this.toString(); }", + "class C extends B { field = this.foo(); }", + "class C extends B { field = this.foo(); constructor() { super(); } }", + "class C extends B { field = this.foo(); constructor() { } }" // < in this case, initializers are never evaluated. ], invalid: [ diff --git a/eslint/tests/lib/rules/no-throw-literal.js b/eslint/tests/lib/rules/no-throw-literal.js index 3855b58..d50b2ce 100644 --- a/eslint/tests/lib/rules/no-throw-literal.js +++ b/eslint/tests/lib/rules/no-throw-literal.js @@ -30,6 +30,7 @@ ruleTester.run("no-throw-literal", rule, { "throw new foo();", // NewExpression "throw foo.bar;", // MemberExpression "throw foo[bar];", // MemberExpression + { code: "class C { #field; foo() { throw foo.#field; } }", parserOptions: { ecmaVersion: 2022 } }, // MemberExpression "throw foo = new Error();", // AssignmentExpression with the `=` operator { code: "throw foo.bar ||= 'literal'", parserOptions: { ecmaVersion: 2021 } }, // AssignmentExpression with a logical operator { code: "throw foo[bar] ??= 'literal'", parserOptions: { ecmaVersion: 2021 } }, // AssignmentExpression with a logical operator diff --git a/eslint/tests/lib/rules/no-undef-init.js b/eslint/tests/lib/rules/no-undef-init.js index b57b9cd..7f134ba 100644 --- a/eslint/tests/lib/rules/no-undef-init.js +++ b/eslint/tests/lib/rules/no-undef-init.js @@ -22,7 +22,11 @@ ruleTester.run("no-undef-init", rule, { valid: [ "var a;", { code: "const foo = undefined", parserOptions: { ecmaVersion: 6 } }, - "var undefined = 5; var foo = undefined;" + "var undefined = 5; var foo = undefined;", + + // doesn't apply to class fields + { code: "class C { field = undefined; }", parserOptions: { ecmaVersion: 2022 } } + ], invalid: [ { diff --git a/eslint/tests/lib/rules/no-undef.js b/eslint/tests/lib/rules/no-undef.js index 43697ec..956bbc9 100644 --- a/eslint/tests/lib/rules/no-undef.js +++ b/eslint/tests/lib/rules/no-undef.js @@ -91,6 +91,67 @@ ruleTester.run("no-undef", rule, { { code: "import.meta", parserOptions: { ecmaVersion: 2020, sourceType: "module" } + }, + + // class static blocks + { + code: "let a; class C { static {} } a;", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "undef", data: { name: "a" } }] + }, + { + code: "var a; class C { static {} } a;", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "undef", data: { name: "a" } }] + }, + { + code: "a; class C { static {} } var a;", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "undef", data: { name: "a" } }] + }, + { + code: "class C { static { C; } }", + parserOptions: { ecmaVersion: 2022, sourceType: "module" } + }, + { + code: "const C = class { static { C; } }", + parserOptions: { ecmaVersion: 2022, sourceType: "module" } + }, + { + code: "class C { static { a; } } var a;", + parserOptions: { ecmaVersion: 2022, sourceType: "module" } + }, + { + code: "class C { static { a; } } let a;", + parserOptions: { ecmaVersion: 2022, sourceType: "module" } + }, + { + code: "class C { static { var a; a; } }", + parserOptions: { ecmaVersion: 2022, sourceType: "module" } + }, + { + code: "class C { static { a; var a; } }", + parserOptions: { ecmaVersion: 2022, sourceType: "module" } + }, + { + code: "class C { static { a; { var a; } } }", + parserOptions: { ecmaVersion: 2022, sourceType: "module" } + }, + { + code: "class C { static { let a; a; } }", + parserOptions: { ecmaVersion: 2022, sourceType: "module" } + }, + { + code: "class C { static { a; let a; } }", + parserOptions: { ecmaVersion: 2022, sourceType: "module" } + }, + { + code: "class C { static { function a() {} a; } }", + parserOptions: { ecmaVersion: 2022, sourceType: "module" } + }, + { + code: "class C { static { a; function a() {} } }", + parserOptions: { ecmaVersion: 2022, sourceType: "module" } } ], invalid: [ @@ -114,6 +175,99 @@ ruleTester.run("no-undef", rule, { ecmaVersion: 2018 }, errors: [{ messageId: "undef", data: { name: "b" } }] + }, + + // class static blocks + { + code: "class C { static { a; } }", + parserOptions: { + ecmaVersion: 2022 + }, + errors: [{ messageId: "undef", data: { name: "a" } }] + }, + { + code: "class C { static { { let a; } a; } }", + parserOptions: { + ecmaVersion: 2022 + }, + errors: [{ messageId: "undef", data: { name: "a" }, column: 31 }] + }, + { + code: "class C { static { { function a() {} } a; } }", + parserOptions: { + ecmaVersion: 2022 + }, + errors: [{ messageId: "undef", data: { name: "a" }, column: 40 }] + }, + { + code: "class C { static { function foo() { var a; } a; } }", + parserOptions: { + ecmaVersion: 2022 + }, + errors: [{ messageId: "undef", data: { name: "a" }, column: 47 }] + }, + { + code: "class C { static { var a; } static { a; } }", + parserOptions: { + ecmaVersion: 2022 + }, + errors: [{ messageId: "undef", data: { name: "a" }, column: 38 }] + }, + { + code: "class C { static { let a; } static { a; } }", + parserOptions: { + ecmaVersion: 2022 + }, + errors: [{ messageId: "undef", data: { name: "a" }, column: 38 }] + }, + { + code: "class C { static { function a(){} } static { a; } }", + parserOptions: { + ecmaVersion: 2022 + }, + errors: [{ messageId: "undef", data: { name: "a" }, column: 46 }] + }, + { + code: "class C { static { var a; } foo() { a; } }", + parserOptions: { + ecmaVersion: 2022 + }, + errors: [{ messageId: "undef", data: { name: "a" }, column: 37 }] + }, + { + code: "class C { static { let a; } foo() { a; } }", + parserOptions: { + ecmaVersion: 2022 + }, + errors: [{ messageId: "undef", data: { name: "a" }, column: 37 }] + }, + { + code: "class C { static { var a; } [a]; }", + parserOptions: { + ecmaVersion: 2022 + }, + errors: [{ messageId: "undef", data: { name: "a" }, column: 30 }] + }, + { + code: "class C { static { let a; } [a]; }", + parserOptions: { + ecmaVersion: 2022 + }, + errors: [{ messageId: "undef", data: { name: "a" }, column: 30 }] + }, + { + code: "class C { static { function a() {} } [a]; }", + parserOptions: { + ecmaVersion: 2022 + }, + errors: [{ messageId: "undef", data: { name: "a" }, column: 39 }] + }, + { + code: "class C { static { var a; } } a;", + parserOptions: { + ecmaVersion: 2022 + }, + errors: [{ messageId: "undef", data: { name: "a" }, column: 31 }] } ] }); diff --git a/eslint/tests/lib/rules/no-underscore-dangle.js b/eslint/tests/lib/rules/no-underscore-dangle.js index 89f2083..c83a5b3 100644 --- a/eslint/tests/lib/rules/no-underscore-dangle.js +++ b/eslint/tests/lib/rules/no-underscore-dangle.js @@ -69,7 +69,9 @@ ruleTester.run("no-underscore-dangle", rule, { { code: "function foo([_bar] = []) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } }, { code: "function foo( { _bar }) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } }, { code: "function foo( { _bar = 0 } = {}) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 } }, - { code: "function foo(...[_bar]) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 2016 } } + { code: "function foo(...[_bar]) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 2016 } }, + { code: "class foo { _field; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class foo { #_field; }", parserOptions: { ecmaVersion: 2022 } } ], invalid: [ { code: "var _foo = 1", errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_foo" }, type: "VariableDeclarator" }] }, @@ -96,8 +98,17 @@ ruleTester.run("no-underscore-dangle", rule, { { code: "const foo = (_bar = 0) => {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "AssignmentPattern" }] }, { code: "function foo(..._bar) {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "RestElement" }] }, { code: "const foo = { onClick(..._bar) { } }", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "RestElement" }] }, - { code: "const foo = (..._bar) => {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "RestElement" }] } - - + { code: "const foo = (..._bar) => {}", options: [{ allowFunctionParams: false }], parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "_bar" }, type: "RestElement" }] }, + { + code: "class foo { #_bar() {} }", + options: [{ enforceInMethodNames: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "#_bar" } }] + }, { + code: "class foo { #bar_() {} }", + options: [{ enforceInMethodNames: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedUnderscore", data: { identifier: "#bar_" } }] + } ] }); diff --git a/eslint/tests/lib/rules/no-unexpected-multiline.js b/eslint/tests/lib/rules/no-unexpected-multiline.js index 83c7bf6..87044ef 100644 --- a/eslint/tests/lib/rules/no-unexpected-multiline.js +++ b/eslint/tests/lib/rules/no-unexpected-multiline.js @@ -12,6 +12,10 @@ const rule = require("../../../lib/rules/no-unexpected-multiline"), { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester(); ruleTester.run("no-unexpected-multiline", rule, { @@ -140,6 +144,28 @@ ruleTester.run("no-unexpected-multiline", rule, { { code: "var a = b?.\n [a, b, c].forEach(doSomething)", parserOptions: { ecmaVersion: 2020 } + }, + + // Class fields + { + code: "class C { field1\n[field2]; }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { field1\n*gen() {} }", + parserOptions: { ecmaVersion: 2022 } + }, + { + + // ArrowFunctionExpression doesn't connect to computed properties. + code: "class C { field1 = () => {}\n[field2]; }", + parserOptions: { ecmaVersion: 2022 } + }, + { + + // ArrowFunctionExpression doesn't connect to binary operators. + code: "class C { field1 = () => {}\n*gen() {} }", + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -321,6 +347,36 @@ ruleTester.run("no-unexpected-multiline", rule, { messageId: "taggedTemplate" } ] + }, + + // Class fields + { + code: "class C { field1 = obj\n[field2]; }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + line: 2, + column: 1, + endLine: 2, + endColumn: 2, + messageId: "property" + } + ] + }, + { + code: "class C { field1 = function() {}\n[field2]; }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + line: 2, + column: 1, + endLine: 2, + endColumn: 2, + messageId: "property" + } + ] } + + // "class C { field1 = obj\n*gen() {} }" is syntax error: Unexpected token '{' ] }); diff --git a/eslint/tests/lib/rules/no-unmodified-loop-condition.js b/eslint/tests/lib/rules/no-unmodified-loop-condition.js index 70c04d0..90cc45d 100644 --- a/eslint/tests/lib/rules/no-unmodified-loop-condition.js +++ b/eslint/tests/lib/rules/no-unmodified-loop-condition.js @@ -12,6 +12,10 @@ const rule = require("../../../lib/rules/no-unmodified-loop-condition"), { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester(); ruleTester.run("no-unmodified-loop-condition", rule, { diff --git a/eslint/tests/lib/rules/no-unreachable.js b/eslint/tests/lib/rules/no-unreachable.js index e1609c8..03816d7 100644 --- a/eslint/tests/lib/rules/no-unreachable.js +++ b/eslint/tests/lib/rules/no-unreachable.js @@ -65,6 +65,26 @@ ruleTester.run("no-unreachable", rule, { parserOptions: { ecmaVersion: 6 } + }, + { + code: "class C { foo = reachable; }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo = reachable; constructor() {} }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C extends B { foo = reachable; }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C extends B { foo = reachable; constructor() { super(); } }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C extends B { static foo = reachable; constructor() {} }", + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -302,6 +322,67 @@ ruleTester.run("no-unreachable", rule, { endColumn: 22 } ] + }, + + /* + * If `extends` exists, constructor exists, and the constructor doesn't + * contain `super()`, then the fields are unreachable because the + * evaluation of `super()` initializes fields in that case. + * In most cases, such an instantiation throws runtime errors, but + * doesn't throw if the constructor returns a value. + */ + { + code: "class C extends B { foo; constructor() {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unreachableCode", column: 21, endColumn: 25 }] + }, + { + code: "class C extends B { foo = unreachable + code; constructor() {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unreachableCode", column: 21, endColumn: 46 }] + }, + { + code: "class C extends B { foo; bar; constructor() {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unreachableCode", column: 21, endColumn: 30 }] + }, + { + code: "class C extends B { foo; constructor() {} bar; }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "unreachableCode", column: 21, endColumn: 25 }, + { messageId: "unreachableCode", column: 43, endColumn: 47 } + ] + }, + { + code: "(class extends B { foo; constructor() {} bar; })", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "unreachableCode", column: 20, endColumn: 24 }, + { messageId: "unreachableCode", column: 42, endColumn: 46 } + ] + }, + { + code: "class B extends A { x; constructor() { class C extends D { [super().x]; constructor() {} } } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "unreachableCode", column: 60, endColumn: 72 } + ] + }, + { + code: "class B extends A { x; constructor() { class C extends super().x { y; constructor() {} } } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "unreachableCode", column: 68, endColumn: 70 } + ] + }, + { + code: "class B extends A { x; static y; z; static q; constructor() {} }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "unreachableCode", column: 21, endColumn: 23 }, + { messageId: "unreachableCode", column: 34, endColumn: 36 } + ] } ] }); diff --git a/eslint/tests/lib/rules/no-unsafe-optional-chaining.js b/eslint/tests/lib/rules/no-unsafe-optional-chaining.js index 38a4f24..95b87a9 100644 --- a/eslint/tests/lib/rules/no-unsafe-optional-chaining.js +++ b/eslint/tests/lib/rules/no-unsafe-optional-chaining.js @@ -6,9 +6,12 @@ "use strict"; const rule = require("../../../lib/rules/no-unsafe-optional-chaining"); - const { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const parserOptions = { ecmaVersion: 2021, sourceType: "module" diff --git a/eslint/tests/lib/rules/no-unused-expressions.js b/eslint/tests/lib/rules/no-unused-expressions.js index 6f49a0a..7cf11a9 100644 --- a/eslint/tests/lib/rules/no-unused-expressions.js +++ b/eslint/tests/lib/rules/no-unused-expressions.js @@ -190,6 +190,29 @@ ruleTester.run("no-unused-expressions", rule, { options: [{ enforceForJSX: true }], parserOptions: { ecmaFeatures: { jsx: true } }, errors: [{ messageId: "unusedExpression", type: "ExpressionStatement" }] + }, + + // class static blocks do not have directive prologues + { + code: "class C { static { 'use strict'; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unusedExpression", type: "ExpressionStatement" }] + }, + { + code: "class C { static { \n'foo'\n'bar'\n } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "unusedExpression", + type: "ExpressionStatement", + line: 2 + }, + { + messageId: "unusedExpression", + type: "ExpressionStatement", + line: 3 + } + ] } ] }); diff --git a/eslint/tests/lib/rules/no-unused-private-class-members.js b/eslint/tests/lib/rules/no-unused-private-class-members.js new file mode 100644 index 0000000..6e80baa --- /dev/null +++ b/eslint/tests/lib/rules/no-unused-private-class-members.js @@ -0,0 +1,390 @@ +/** + * @fileoverview Tests for no-unused-private-class-members rule. + * @author Tim van der Lippe + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const rule = require("../../../lib/rules/no-unused-private-class-members"), + { RuleTester } = require("../../../lib/rule-tester"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); + +/** + * Returns an expected error for defined-but-not-used private class member. + * @param {string} classMemberName The name of the class member + * @returns {Object} An expected error object + */ +function definedError(classMemberName) { + return { + messageId: "unusedPrivateClassMember", + data: { + classMemberName: `#${classMemberName}` + } + }; +} + +ruleTester.run("no-unused-private-class-members", rule, { + valid: [ + "class Foo {}", + `class Foo { + publicMember = 42; +}`, + `class Foo { + #usedMember = 42; + method() { + return this.#usedMember; + } +}`, + `class Foo { + #usedMember = 42; + anotherMember = this.#usedMember; +}`, + `class Foo { + #usedMember = 42; + foo() { + anotherMember = this.#usedMember; + } +}`, + `class C { + #usedMember; + + foo() { + bar(this.#usedMember += 1); + } +}`, + `class Foo { + #usedMember = 42; + method() { + return someGlobalMethod(this.#usedMember); + } +}`, + `class C { + #usedInOuterClass; + + foo() { + return class {}; + } + + bar() { + return this.#usedInOuterClass; + } +}`, + `class Foo { + #usedInForInLoop; + method() { + for (const bar in this.#usedInForInLoop) { + + } + } +}`, + `class Foo { + #usedInForOfLoop; + method() { + for (const bar of this.#usedInForOfLoop) { + + } + } +}`, + `class Foo { + #usedInAssignmentPattern; + method() { + [bar = 1] = this.#usedInAssignmentPattern; + } +}`, + `class Foo { + #usedInArrayPattern; + method() { + [bar] = this.#usedInArrayPattern; + } +}`, + `class Foo { + #usedInAssignmentPattern; + method() { + [bar] = this.#usedInAssignmentPattern; + } +}`, + `class C { + #usedInObjectAssignment; + + method() { + ({ [this.#usedInObjectAssignment]: a } = foo); + } +}`, + `class C { + set #accessorWithSetterFirst(value) { + doSomething(value); + } + get #accessorWithSetterFirst() { + return something(); + } + method() { + this.#accessorWithSetterFirst += 1; + } +}`, + `class Foo { + set #accessorUsedInMemberAccess(value) {} + + method(a) { + [this.#accessorUsedInMemberAccess] = a; + } +}`, + `class C { + get #accessorWithGetterFirst() { + return something(); + } + set #accessorWithGetterFirst(value) { + doSomething(value); + } + method() { + this.#accessorWithGetterFirst += 1; + } +}`, + `class C { + #usedInInnerClass; + + method(a) { + return class { + foo = a.#usedInInnerClass; + } + } +}`, + + //-------------------------------------------------------------------------- + // Method definitions + //-------------------------------------------------------------------------- + `class Foo { + #usedMethod() { + return 42; + } + anotherMethod() { + return this.#usedMethod(); + } +}`, + `class C { + set #x(value) { + doSomething(value); + } + + foo() { + this.#x = 1; + } +}` + ], + invalid: [ + { + code: `class Foo { + #unusedMember = 5; +}`, + errors: [definedError("unusedMember")] + }, + { + code: `class First {} +class Second { + #unusedMemberInSecondClass = 5; +}`, + errors: [definedError("unusedMemberInSecondClass")] + }, + { + code: `class First { + #unusedMemberInFirstClass = 5; +} +class Second {}`, + errors: [definedError("unusedMemberInFirstClass")] + }, + { + code: `class First { + #firstUnusedMemberInSameClass = 5; + #secondUnusedMemberInSameClass = 5; +}`, + errors: [definedError("firstUnusedMemberInSameClass"), definedError("secondUnusedMemberInSameClass")] + }, + { + code: `class Foo { + #usedOnlyInWrite = 5; + method() { + this.#usedOnlyInWrite = 42; + } +}`, + errors: [definedError("usedOnlyInWrite")] + }, + { + code: `class Foo { + #usedOnlyInWriteStatement = 5; + method() { + this.#usedOnlyInWriteStatement += 42; + } +}`, + errors: [definedError("usedOnlyInWriteStatement")] + }, + { + code: `class C { + #usedOnlyInIncrement; + + foo() { + this.#usedOnlyInIncrement++; + } +}`, + errors: [definedError("usedOnlyInIncrement")] + }, + { + code: `class C { + #unusedInOuterClass; + + foo() { + return class { + #unusedInOuterClass; + + bar() { + return this.#unusedInOuterClass; + } + }; + } +}`, + errors: [definedError("unusedInOuterClass")] + }, + { + code: `class C { + #unusedOnlyInSecondNestedClass; + + foo() { + return class { + #unusedOnlyInSecondNestedClass; + + bar() { + return this.#unusedOnlyInSecondNestedClass; + } + }; + } + + baz() { + return this.#unusedOnlyInSecondNestedClass; + } + + bar() { + return class { + #unusedOnlyInSecondNestedClass; + } + } +}`, + errors: [definedError("unusedOnlyInSecondNestedClass")] + }, + + //-------------------------------------------------------------------------- + // Unused method definitions + //-------------------------------------------------------------------------- + { + code: `class Foo { + #unusedMethod() {} +}`, + errors: [definedError("unusedMethod")] + }, + { + code: `class Foo { + #unusedMethod() {} + #usedMethod() { + return 42; + } + publicMethod() { + return this.#usedMethod(); + } +}`, + errors: [definedError("unusedMethod")] + }, + { + code: `class Foo { + set #unusedSetter(value) {} +}`, + errors: [definedError("unusedSetter")] + }, + { + code: `class Foo { + #unusedForInLoop; + method() { + for (this.#unusedForInLoop in bar) { + + } + } +}`, + errors: [definedError("unusedForInLoop")] + }, + { + code: `class Foo { + #unusedForOfLoop; + method() { + for (this.#unusedForOfLoop of bar) { + + } + } +}`, + errors: [definedError("unusedForOfLoop")] + }, + { + code: `class Foo { + #unusedInDestructuring; + method() { + ({ x: this.#unusedInDestructuring } = bar); + } +}`, + errors: [definedError("unusedInDestructuring")] + }, + { + code: `class Foo { + #unusedInRestPattern; + method() { + [...this.#unusedInRestPattern] = bar; + } +}`, + errors: [definedError("unusedInRestPattern")] + }, + { + code: `class Foo { + #unusedInAssignmentPattern; + method() { + [this.#unusedInAssignmentPattern = 1] = bar; + } +}`, + errors: [definedError("unusedInAssignmentPattern")] + }, + { + code: `class Foo { + #unusedInAssignmentPattern; + method() { + [this.#unusedInAssignmentPattern] = bar; + } +}`, + errors: [definedError("unusedInAssignmentPattern")] + }, + { + code: `class C { + #usedOnlyInTheSecondInnerClass; + + method(a) { + return class { + #usedOnlyInTheSecondInnerClass; + + method2(b) { + foo = b.#usedOnlyInTheSecondInnerClass; + } + + method3(b) { + foo = b.#usedOnlyInTheSecondInnerClass; + } + } + } +}`, + errors: [{ + ...definedError("usedOnlyInTheSecondInnerClass"), + line: 2 + }] + } + ] +}); diff --git a/eslint/tests/lib/rules/no-unused-vars.js b/eslint/tests/lib/rules/no-unused-vars.js index 48ccdb1..7c5c871 100644 --- a/eslint/tests/lib/rules/no-unused-vars.js +++ b/eslint/tests/lib/rules/no-unused-vars.js @@ -180,6 +180,7 @@ ruleTester.run("no-unused-vars", rule, { // Sequence Expressions (See https://github.com/eslint/eslint/issues/14325) { code: "let x = 0; foo = (0, x++);", parserOptions: { ecmaVersion: 6 } }, { code: "let x = 0; foo = (0, x += 1);", parserOptions: { ecmaVersion: 6 } }, + { code: "let x = 0; foo = (0, x = x + 1);", parserOptions: { ecmaVersion: 6 } }, // caughtErrors { @@ -1064,6 +1065,55 @@ ruleTester.run("no-unused-vars", rule, { parserOptions: { ecmaVersion: 2015 }, errors: [{ ...assignedError("x"), line: 1, column: 23 }] }, + + // https://github.com/eslint/eslint/issues/14866 + { + code: `let z = 0; + z = z + 1, z = 2; + `, + parserOptions: { ecmaVersion: 2020 }, + errors: [{ ...assignedError("z"), line: 2, column: 24 }] + }, + { + code: `let z = 0; + z = z+1, z = 2; + z = 3;`, + parserOptions: { ecmaVersion: 2020 }, + errors: [{ ...assignedError("z"), line: 3, column: 13 }] + }, + { + code: `let z = 0; + z = z+1, z = 2; + z = z+3; + `, + parserOptions: { ecmaVersion: 2020 }, + errors: [{ ...assignedError("z"), line: 3, column: 13 }] + }, + { + code: "let x = 0; 0, x = x+1;", + parserOptions: { ecmaVersion: 2020 }, + errors: [{ ...assignedError("x"), line: 1, column: 15 }] + }, + { + code: "let x = 0; x = x+1, 0;", + parserOptions: { ecmaVersion: 2020 }, + errors: [{ ...assignedError("x"), line: 1, column: 12 }] + }, + { + code: "let x = 0; foo = ((0, x = x + 1), 0);", + parserOptions: { ecmaVersion: 2020 }, + errors: [{ ...assignedError("x"), line: 1, column: 23 }] + }, + { + code: "let x = 0; foo = (x = x+1, 0);", + parserOptions: { ecmaVersion: 2020 }, + errors: [{ ...assignedError("x"), line: 1, column: 19 }] + }, + { + code: "let x = 0; 0, (1, x=x+1);", + parserOptions: { ecmaVersion: 2020 }, + errors: [{ ...assignedError("x"), line: 1, column: 19 }] + }, { code: "(function ({ a, b }, { c } ) { return b; })();", parserOptions: { ecmaVersion: 2015 }, diff --git a/eslint/tests/lib/rules/no-use-before-define.js b/eslint/tests/lib/rules/no-use-before-define.js index 152fa99..ba80e40 100644 --- a/eslint/tests/lib/rules/no-use-before-define.js +++ b/eslint/tests/lib/rules/no-use-before-define.js @@ -20,6 +20,9 @@ const ruleTester = new RuleTester(); ruleTester.run("no-use-before-define", rule, { valid: [ + "unresolved", + "Array", + "function foo () { arguments; }", "var a=10; alert(a);", "function b(a) { alert(a); }", "Object.hasOwnProperty.call(a);", @@ -35,6 +38,8 @@ ruleTester.run("no-use-before-define", rule, { "var foo = function() { foo(); };", "var a; for (a in a) {}", { code: "var a; for (a of a) {}", parserOptions: { ecmaVersion: 6 } }, + { code: "let a; class C { static { a; } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { let a; a; } }", parserOptions: { ecmaVersion: 2022 } }, // Block-level bindings { code: "\"use strict\"; a(); { function a() {} }", parserOptions: { ecmaVersion: 6 } }, @@ -56,6 +61,154 @@ ruleTester.run("no-use-before-define", rule, { code: "var foo = () => bar; var bar;", options: [{ variables: false }], parserOptions: { ecmaVersion: 6 } + }, + { + code: "class C { static { () => foo; let foo; } }", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 } + }, + + // Tests related to class definition evaluation. These are not TDZ errors. + { code: "class C extends (class { method() { C; } }) {}", parserOptions: { ecmaVersion: 6 } }, + { code: "(class extends (class { method() { C; } }) {});", parserOptions: { ecmaVersion: 6 } }, + { code: "const C = (class extends (class { method() { C; } }) {});", parserOptions: { ecmaVersion: 6 } }, + { code: "class C extends (class { field = C; }) {}", parserOptions: { ecmaVersion: 2022 } }, + { code: "(class extends (class { field = C; }) {});", parserOptions: { ecmaVersion: 2022 } }, + { code: "const C = (class extends (class { field = C; }) {});", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { [() => C](){} }", parserOptions: { ecmaVersion: 6 } }, + { code: "(class C { [() => C](){} });", parserOptions: { ecmaVersion: 6 } }, + { code: "const C = class { [() => C](){} };", parserOptions: { ecmaVersion: 6 } }, + { code: "class C { static [() => C](){} }", parserOptions: { ecmaVersion: 6 } }, + { code: "(class C { static [() => C](){} });", parserOptions: { ecmaVersion: 6 } }, + { code: "const C = class { static [() => C](){} };", parserOptions: { ecmaVersion: 6 } }, + { code: "class C { [() => C]; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "(class C { [() => C]; });", parserOptions: { ecmaVersion: 2022 } }, + { code: "const C = class { [() => C]; };", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static [() => C]; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "(class C { static [() => C]; });", parserOptions: { ecmaVersion: 2022 } }, + { code: "const C = class { static [() => C]; };", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { method() { C; } }", parserOptions: { ecmaVersion: 6 } }, + { code: "(class C { method() { C; } });", parserOptions: { ecmaVersion: 6 } }, + { code: "const C = class { method() { C; } };", parserOptions: { ecmaVersion: 6 } }, + { code: "class C { static method() { C; } }", parserOptions: { ecmaVersion: 6 } }, + { code: "(class C { static method() { C; } });", parserOptions: { ecmaVersion: 6 } }, + { code: "const C = class { static method() { C; } };", parserOptions: { ecmaVersion: 6 } }, + { code: "class C { field = C; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "(class C { field = C; });", parserOptions: { ecmaVersion: 2022 } }, + { code: "const C = class { field = C; };", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static field = C; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "(class C { static field = C; });", parserOptions: { ecmaVersion: 2022 } }, // `const C = class { static field = C; };` is TDZ error + { code: "class C { static field = class { static field = C; }; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "(class C { static field = class { static field = C; }; });", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { field = () => C; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "(class C { field = () => C; });", parserOptions: { ecmaVersion: 2022 } }, + { code: "const C = class { field = () => C; };", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static field = () => C; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "(class C { static field = () => C; });", parserOptions: { ecmaVersion: 2022 } }, + { code: "const C = class { static field = () => C; };", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { field = class extends C {}; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "(class C { field = class extends C {}; });", parserOptions: { ecmaVersion: 2022 } }, + { code: "const C = class { field = class extends C {}; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static field = class extends C {}; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "(class C { static field = class extends C {}; });", parserOptions: { ecmaVersion: 2022 } }, // `const C = class { static field = class extends C {}; };` is TDZ error + { code: "class C { static field = class { [C]; }; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "(class C { static field = class { [C]; }; });", parserOptions: { ecmaVersion: 2022 } }, // `const C = class { static field = class { [C]; } };` is TDZ error + { code: "const C = class { static field = class { field = C; }; };", parserOptions: { ecmaVersion: 2022 } }, + { + code: "class C { method() { a; } } let a;", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "class C { static method() { a; } } let a;", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "class C { field = a; } let a;", // `class C { static field = a; } let a;` is TDZ error + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { field = D; } class D {}", // `class C { static field = D; } class D {}` is TDZ error + options: [{ classes: false }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { field = class extends D {}; } class D {}", // `class C { static field = class extends D {}; } class D {}` is TDZ error + options: [{ classes: false }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { field = () => a; } let a;", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static field = () => a; } let a;", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { field = () => D; } class D {}", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static field = () => D; } class D {}", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static field = class { field = a; }; } let a;", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { C; } }", // `const C = class { static { C; } }` is TDZ error + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { C; } static {} static { C; } }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "(class C { static { C; } })", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { class D extends C {} } }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { (class { static { C } }) } }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { () => C; } }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "(class C { static { () => C; } })", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "const C = class { static { () => C; } }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { () => D; } } class D {}", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { () => a; } } let a;", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "const C = class C { static { C.x; } }", + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -495,6 +648,480 @@ ruleTester.run("no-use-before-define", rule, { messageId: "usedBeforeDefined", data: { name: "x" } }] + }, + + // Tests related to class definition evaluation. These are TDZ errors. + { + code: "class C extends C {}", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "const C = class extends C {};", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "class C extends (class { [C](){} }) {}", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "const C = class extends (class { [C](){} }) {};", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "class C extends (class { static field = C; }) {}", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "const C = class extends (class { static field = C; }) {};", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "class C { [C](){} }", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "(class C { [C](){} });", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "const C = class { [C](){} };", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "class C { static [C](){} }", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "(class C { static [C](){} });", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "const C = class { static [C](){} };", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "class C { [C]; }", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "(class C { [C]; });", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "const C = class { [C]; };", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "class C { [C] = foo; }", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "(class C { [C] = foo; });", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "const C = class { [C] = foo; };", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "class C { static [C]; }", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "(class C { static [C]; });", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "const C = class { static [C]; };", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "class C { static [C] = foo; }", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "(class C { static [C] = foo; });", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "const C = class { static [C] = foo; };", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "const C = class { static field = C; };", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "const C = class { static field = class extends C {}; };", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "const C = class { static field = class { [C]; } };", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "const C = class { static field = class { static field = C; }; };", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "class C extends D {} class D {}", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "D" } + }] + }, + { + code: "class C extends (class { [a](){} }) {} let a;", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "a" } + }] + }, + { + code: "class C extends (class { static field = a; }) {} let a;", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "a" } + }] + }, + { + code: "class C { [a]() {} } let a;", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "a" } + }] + }, + { + code: "class C { static [a]() {} } let a;", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "a" } + }] + }, + { + code: "class C { [a]; } let a;", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "a" } + }] + }, + { + code: "class C { static [a]; } let a;", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "a" } + }] + }, + { + code: "class C { [a] = foo; } let a;", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "a" } + }] + }, + { + code: "class C { static [a] = foo; } let a;", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "a" } + }] + }, + { + code: "class C { static field = a; } let a;", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "a" } + }] + }, + { + code: "class C { static field = D; } class D {}", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "D" } + }] + }, + { + code: "class C { static field = class extends D {}; } class D {}", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "D" } + }] + }, + { + code: "class C { static field = class { [a](){} } } let a;", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "a" } + }] + }, + { + code: "class C { static field = class { static field = a; }; } let a;", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "a" } + }] + }, + { + code: "const C = class { static { C; } };", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "const C = class { static { (class extends C {}); } };", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "C" } + }] + }, + { + code: "class C { static { a; } } let a;", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "a" } + }] + }, + { + code: "class C { static { D; } } class D {}", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "D" } + }] + }, + { + code: "class C { static { (class extends D {}); } } class D {}", + options: [{ classes: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "D" } + }] + }, + { + code: "class C { static { (class { [a](){} }); } } let a;", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "a" } + }] + }, + { + code: "class C { static { (class { static field = a; }); } } let a;", + options: [{ variables: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "usedBeforeDefined", + data: { name: "a" } + }] } + + /* + * TODO(mdjermanovic): Add the following test cases once https://github.com/eslint/eslint-scope/issues/59 gets fixed: + * { + * code: "(class C extends C {});", + * options: [{ classes: false }], + * parserOptions: { ecmaVersion: 6 }, + * errors: [{ + * messageId: "usedBeforeDefined", + * data: { name: "C" } + * }] + * }, + * { + * code: "(class C extends (class { [C](){} }) {});", + * options: [{ classes: false }], + * parserOptions: { ecmaVersion: 6 }, + * errors: [{ + * messageId: "usedBeforeDefined", + * data: { name: "C" } + * }] + * }, + * { + * code: "(class C extends (class { static field = C; }) {});", + * options: [{ classes: false }], + * parserOptions: { ecmaVersion: 2022 }, + * errors: [{ + * messageId: "usedBeforeDefined", + * data: { name: "C" } + * }] + * } + */ ] }); diff --git a/eslint/tests/lib/rules/no-useless-call.js b/eslint/tests/lib/rules/no-useless-call.js index 528f2f1..43f9b7d 100644 --- a/eslint/tests/lib/rules/no-useless-call.js +++ b/eslint/tests/lib/rules/no-useless-call.js @@ -50,6 +50,12 @@ ruleTester.run("no-useless-call", rule, { { code: "obj?.foo.bar.call(obj.foo, 1, 2);", parserOptions: { ecmaVersion: 2020 } + }, + + // Private members + { + code: "class C { #call; wrap(foo) { foo.#call(undefined, 1, 2); } }", + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ diff --git a/eslint/tests/lib/rules/no-useless-computed-key.js b/eslint/tests/lib/rules/no-useless-computed-key.js index 100899c..37f3111 100644 --- a/eslint/tests/lib/rules/no-useless-computed-key.js +++ b/eslint/tests/lib/rules/no-useless-computed-key.js @@ -16,7 +16,7 @@ const rule = require("../../../lib/rules/no-useless-computed-key"), // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); ruleTester.run("no-useless-computed-key", rule, { valid: [ @@ -41,6 +41,10 @@ ruleTester.run("no-useless-computed-key", rule, { { code: "(class { ['x']() {} })", options: [{ enforceForClassMembers: false }] }, { code: "class Foo { static ['constructor']() {} }", options: [{ enforceForClassMembers: false }] }, { code: "class Foo { ['prototype']() {} }", options: [{ enforceForClassMembers: false }] }, + { code: "class Foo { a }", options: [{ enforceForClassMembers: true }] }, + { code: "class Foo { ['constructor'] }", options: [{ enforceForClassMembers: true }] }, + { code: "class Foo { static ['constructor'] }", options: [{ enforceForClassMembers: true }] }, + { code: "class Foo { static ['prototype'] }", options: [{ enforceForClassMembers: true }] }, /* * Well-known browsers throw syntax error bigint literals on property names, @@ -240,6 +244,22 @@ ruleTester.run("no-useless-computed-key", rule, { data: { property: "2" }, type: "Property" }] + }, { + code: "({ ['constructor']: 1 })", + output: "({ 'constructor': 1 })", + errors: [{ + messageId: "unnecessarilyComputedProperty", + data: { property: "'constructor'" }, + type: "Property" + }] + }, { + code: "({ ['prototype']: 1 })", + output: "({ 'prototype': 1 })", + errors: [{ + messageId: "unnecessarilyComputedProperty", + data: { property: "'prototype'" }, + type: "Property" + }] }, { code: "class Foo { ['0']() {} }", output: "class Foo { '0'() {} }", @@ -460,6 +480,24 @@ ruleTester.run("no-useless-computed-key", rule, { data: { property: "'x'" }, type: "MethodDefinition" }] + }, { + code: "(class { ['__proto__']() {} })", + output: "(class { '__proto__'() {} })", + options: [{ enforceForClassMembers: true }], + errors: [{ + messageId: "unnecessarilyComputedProperty", + data: { property: "'__proto__'" }, + type: "MethodDefinition" + }] + }, { + code: "(class { static ['__proto__']() {} })", + output: "(class { static '__proto__'() {} })", + options: [{ enforceForClassMembers: true }], + errors: [{ + messageId: "unnecessarilyComputedProperty", + data: { property: "'__proto__'" }, + type: "MethodDefinition" + }] }, { code: "(class { static ['constructor']() {} })", output: "(class { static 'constructor'() {} })", @@ -478,6 +516,69 @@ ruleTester.run("no-useless-computed-key", rule, { data: { property: "'prototype'" }, type: "MethodDefinition" }] + }, { + code: "class Foo { ['0'] }", + output: "class Foo { '0' }", + options: [{ enforceForClassMembers: true }], + errors: [{ + messageId: "unnecessarilyComputedProperty", + data: { property: "'0'" }, + type: "PropertyDefinition" + }] + }, { + code: "class Foo { ['0'] = 0 }", + output: "class Foo { '0' = 0 }", + options: [{ enforceForClassMembers: true }], + errors: [{ + messageId: "unnecessarilyComputedProperty", + data: { property: "'0'" }, + type: "PropertyDefinition" + }] + }, { + code: "class Foo { static[0] }", + output: "class Foo { static 0 }", + options: [{ enforceForClassMembers: true }], + errors: [{ + messageId: "unnecessarilyComputedProperty", + data: { property: "0" }, + type: "PropertyDefinition" + }] + }, { + code: "class Foo { ['#foo'] }", + output: "class Foo { '#foo' }", + options: [{ enforceForClassMembers: true }], + errors: [{ + messageId: "unnecessarilyComputedProperty", + data: { property: "'#foo'" }, + type: "PropertyDefinition" + }] + }, { + code: "(class { ['__proto__'] })", + output: "(class { '__proto__' })", + options: [{ enforceForClassMembers: true }], + errors: [{ + messageId: "unnecessarilyComputedProperty", + data: { property: "'__proto__'" }, + type: "PropertyDefinition" + }] + }, { + code: "(class { static ['__proto__'] })", + output: "(class { static '__proto__' })", + options: [{ enforceForClassMembers: true }], + errors: [{ + messageId: "unnecessarilyComputedProperty", + data: { property: "'__proto__'" }, + type: "PropertyDefinition" + }] + }, { + code: "(class { ['prototype'] })", + output: "(class { 'prototype' })", + options: [{ enforceForClassMembers: true }], + errors: [{ + messageId: "unnecessarilyComputedProperty", + data: { property: "'prototype'" }, + type: "PropertyDefinition" + }] } ] }); diff --git a/eslint/tests/lib/rules/no-useless-concat.js b/eslint/tests/lib/rules/no-useless-concat.js index c15b88f..f19a196 100644 --- a/eslint/tests/lib/rules/no-useless-concat.js +++ b/eslint/tests/lib/rules/no-useless-concat.js @@ -9,7 +9,6 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/no-useless-concat"), - { RuleTester } = require("../../../lib/rule-tester"); diff --git a/eslint/tests/lib/rules/no-useless-escape.js b/eslint/tests/lib/rules/no-useless-escape.js index ac3294b..09f146e 100644 --- a/eslint/tests/lib/rules/no-useless-escape.js +++ b/eslint/tests/lib/rules/no-useless-escape.js @@ -12,6 +12,10 @@ const rule = require("../../../lib/rules/no-useless-escape"), { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester(); ruleTester.run("no-useless-escape", rule, { diff --git a/eslint/tests/lib/rules/no-useless-rename.js b/eslint/tests/lib/rules/no-useless-rename.js index f09a9d4..66077f2 100644 --- a/eslint/tests/lib/rules/no-useless-rename.js +++ b/eslint/tests/lib/rules/no-useless-rename.js @@ -12,6 +12,10 @@ const rule = require("../../../lib/rules/no-useless-rename"), { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6, sourceType: "module" } }); ruleTester.run("no-useless-rename", rule, { diff --git a/eslint/tests/lib/rules/one-var.js b/eslint/tests/lib/rules/one-var.js index 3388c9d..935c464 100644 --- a/eslint/tests/lib/rules/one-var.js +++ b/eslint/tests/lib/rules/one-var.js @@ -14,6 +14,10 @@ const rule = require("../../../lib/rules/one-var"), const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } }); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + ruleTester.run("one-var", rule, { valid: [ "function foo() { var bar = true; }", @@ -489,6 +493,143 @@ ruleTester.run("one-var", rule, { { code: "var foo = 1;\nlet bar = function() { var x; };\nvar baz = 2;", options: [{ var: "never" }] + }, + + // class static blocks + { + code: "class C { static { var a; let b; const c = 0; } }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "const a = 0; class C { static { const b = 0; } }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { const b = 0; } } const a = 0; ", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "let a; class C { static { let b; } }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { let b; } } let a;", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "var a; class C { static { var b; } }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { var b; } } var a; ", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "var a; class C { static { if (foo) { var b; } } }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { if (foo) { var b; } } } var a; ", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { const a = 0; if (foo) { const b = 0; } } }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { let a; if (foo) { let b; } } }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { const a = 0; const b = 0; } }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { let a; let b; } }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { var a; var b; } }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { let a; foo; let b; } }", + options: ["consecutive"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { let a; const b = 0; let c; } }", + options: ["consecutive"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { var a; foo; var b; } }", + options: ["consecutive"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { var a; let b; var c; } }", + options: ["consecutive"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { let a; if (foo) { let b; } } }", + options: ["consecutive"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { if (foo) { let b; } let a; } }", + options: ["consecutive"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { const a = 0; if (foo) { const b = 0; } } }", + options: ["consecutive"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { if (foo) { const b = 0; } const a = 0; } }", + options: ["consecutive"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { var a; if (foo) var b; } }", + options: ["consecutive"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { if (foo) var b; var a; } }", + options: ["consecutive"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { if (foo) { var b; } var a; } }", + options: ["consecutive"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { let a; let b = 0; } }", + options: [{ initialized: "consecutive" }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { var a; var b = 0; } }", + options: [{ initialized: "consecutive" }], + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -2114,6 +2255,129 @@ ruleTester.run("one-var", rule, { data: { type: "var" }, type: "VariableDeclaration" }] + }, + + // class static blocks + { + code: "class C { static { let x, y; } }", + output: "class C { static { let x; let y; } }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "split", + data: { type: "let" }, + type: "VariableDeclaration" + }] + }, + { + code: "class C { static { var x, y; } }", + output: "class C { static { var x; var y; } }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "split", + data: { type: "var" }, + type: "VariableDeclaration" + }] + }, + { + code: "class C { static { let x; let y; } }", + output: "class C { static { let x, y; } }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "combine", + data: { type: "let" }, + type: "VariableDeclaration" + }] + }, + { + code: "class C { static { var x; var y; } }", + output: "class C { static { var x, y; } }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "combine", + data: { type: "var" }, + type: "VariableDeclaration" + }] + }, + { + code: "class C { static { let x; foo; let y; } }", + output: null, + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "combine", + data: { type: "let" }, + type: "VariableDeclaration" + }] + }, + { + code: "class C { static { var x; foo; var y; } }", + output: null, + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "combine", + data: { type: "var" }, + type: "VariableDeclaration" + }] + }, + { + code: "class C { static { var x; if (foo) { var y; } } }", + output: null, + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "combine", + data: { type: "var" }, + type: "VariableDeclaration" + }] + }, + { + code: "class C { static { let x; let y; } }", + output: "class C { static { let x, y; } }", + options: ["consecutive"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "combine", + data: { type: "let" }, + type: "VariableDeclaration" + }] + }, + { + code: "class C { static { var x; var y; } }", + output: "class C { static { var x, y; } }", + options: ["consecutive"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "combine", + data: { type: "var" }, + type: "VariableDeclaration" + }] + }, + { + code: "class C { static { let a = 0; let b = 1; } }", + output: "class C { static { let a = 0, b = 1; } }", + options: [{ initialized: "consecutive" }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "combineInitialized", + data: { type: "let" }, + type: "VariableDeclaration" + }] + }, + { + code: "class C { static { var a = 0; var b = 1; } }", + output: "class C { static { var a = 0, b = 1; } }", + options: [{ initialized: "consecutive" }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "combineInitialized", + data: { type: "var" }, + type: "VariableDeclaration" + }] } ] }); diff --git a/eslint/tests/lib/rules/operator-assignment.js b/eslint/tests/lib/rules/operator-assignment.js index 1d07b0c..8f81484 100644 --- a/eslint/tests/lib/rules/operator-assignment.js +++ b/eslint/tests/lib/rules/operator-assignment.js @@ -16,7 +16,7 @@ const rule = require("../../../lib/rules/operator-assignment"), // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2021 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); const EXPECTED_OPERATOR_ASSIGNMENT = [{ messageId: "replaced", type: "AssignmentExpression" }]; const UNEXPECTED_OPERATOR_ASSIGNMENT = [{ messageId: "unexpected", type: "AssignmentExpression" }]; @@ -85,6 +85,7 @@ ruleTester.run("operator-assignment", rule, { code: "this.x = foo.this.x + y", options: ["always"] }, + "const foo = 0; class C { foo = foo + 1; }", // does not check logical operators { diff --git a/eslint/tests/lib/rules/operator-linebreak.js b/eslint/tests/lib/rules/operator-linebreak.js index 1eeb9f5..8810c04 100644 --- a/eslint/tests/lib/rules/operator-linebreak.js +++ b/eslint/tests/lib/rules/operator-linebreak.js @@ -56,6 +56,7 @@ ruleTester.run("operator-linebreak", rule, { { code: "\n1 + 1", options: ["none"] }, { code: "1 + 1\n", options: ["none"] }, { code: "answer = everything ? 42 : foo;", options: ["none"] }, + { code: "(a\n) + (\nb)", options: ["none"] }, { code: "answer = everything \n?\n 42 : foo;", options: [null, { overrides: { "?": "ignore" } }] }, { code: "answer = everything ? 42 \n:\n foo;", options: [null, { overrides: { ":": "ignore" } }] }, @@ -98,6 +99,52 @@ ruleTester.run("operator-linebreak", rule, { code: "a ??= \n b", options: ["after", { overrides: { "??": "before" } }], parserOptions: { ecmaVersion: 2021 } + }, + + // class fields + { + code: "class C { foo =\n0 }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo\n= 0 }", + options: ["before"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { [foo\n]= 0 }", + options: ["before"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { [foo]\n= 0 }", + options: ["before"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { [foo\n]\n= 0 }", + options: ["before"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { [foo\n]= 0 }", + options: ["after"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { [foo\n]=\n0 }", + options: ["after"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { [foo\n]= 0 }", + options: ["none"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo\n=\n0 }", + options: ["none", { overrides: { "=": "ignore" } }], + parserOptions: { ecmaVersion: 2022 } } ], @@ -770,6 +817,98 @@ ruleTester.run("operator-linebreak", rule, { endLine: 2, endColumn: 4 }] + }, + + // class fields + { + code: "class C { a\n= 0; }", + output: "class C { a =\n0; }", + options: ["after"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "operatorAtEnd", + data: { operator: "=" }, + type: "PropertyDefinition", + line: 2, + column: 1, + endLine: 2, + endColumn: 2 + }] + }, + { + code: "class C { a =\n0; }", + output: "class C { a\n= 0; }", + options: ["before"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "operatorAtBeginning", + data: { operator: "=" }, + type: "PropertyDefinition", + line: 1, + column: 13, + endLine: 1, + endColumn: 14 + }] + }, + { + code: "class C { a =\n0; }", + output: "class C { a =0; }", + options: ["none"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noLinebreak", + data: { operator: "=" }, + type: "PropertyDefinition", + line: 1, + column: 13, + endLine: 1, + endColumn: 14 + }] + }, + { + code: "class C { [a]\n= 0; }", + output: "class C { [a] =\n0; }", + options: ["after"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "operatorAtEnd", + data: { operator: "=" }, + type: "PropertyDefinition", + line: 2, + column: 1, + endLine: 2, + endColumn: 2 + }] + }, + { + code: "class C { [a] =\n0; }", + output: "class C { [a]\n= 0; }", + options: ["before"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "operatorAtBeginning", + data: { operator: "=" }, + type: "PropertyDefinition", + line: 1, + column: 15, + endLine: 1, + endColumn: 16 + }] + }, + { + code: "class C { [a]\n =0; }", + output: "class C { [a] =0; }", + options: ["none"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "noLinebreak", + data: { operator: "=" }, + type: "PropertyDefinition", + line: 2, + column: 2, + endLine: 2, + endColumn: 3 + }] } ] }); diff --git a/eslint/tests/lib/rules/padded-blocks.js b/eslint/tests/lib/rules/padded-blocks.js index 1e15da4..3dd0dc9 100644 --- a/eslint/tests/lib/rules/padded-blocks.js +++ b/eslint/tests/lib/rules/padded-blocks.js @@ -74,6 +74,9 @@ ruleTester.run("padded-blocks", rule, { { code: "class A{\nfoo(){}\n}", options: ["never"], parserOptions: { ecmaVersion: 6 } }, { code: "class A{\nfoo(){}\n}", options: [{ classes: "never" }], parserOptions: { ecmaVersion: 6 } }, + { code: "class A{\n\nfoo;\n\n}", parserOptions: { ecmaVersion: 2022 } }, + { code: "class A{\nfoo;\n}", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + // Ignore block statements if not configured { code: "{\na();\n}", options: [{ switches: "always" }] }, { code: "{\n\na();\n\n}", options: [{ switches: "never" }] }, @@ -85,7 +88,109 @@ ruleTester.run("padded-blocks", rule, { // Ignore class statements if not configured { code: "class A{\nfoo(){}\n}", options: [{ blocks: "always" }], parserOptions: { ecmaVersion: 6 } }, - { code: "class A{\n\nfoo(){}\n\n}", options: [{ blocks: "never" }], parserOptions: { ecmaVersion: 6 } } + { code: "class A{\n\nfoo(){}\n\n}", options: [{ blocks: "never" }], parserOptions: { ecmaVersion: 6 } }, + + // class static blocks + { + code: "class C {\n\n static {\n\nfoo;\n\n} \n\n}", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {\n\n static {// comment\n\nfoo;\n\n/* comment */} \n\n}", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {\n\n static {\n\n// comment\nfoo;\n// comment\n\n} \n\n}", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {\n\n static {\n\n// comment\n\nfoo;\n\n// comment\n\n} \n\n}", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {\n\n static { foo; } \n\n}", + options: ["always", { allowSingleLineBlocks: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {\n\n static\n { foo; } \n\n}", + options: ["always", { allowSingleLineBlocks: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {\n\n static {} static {\n} static {\n\n} \n\n}", // empty blocks are ignored + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {\n\n static\n\n { foo; } \n\n}", + options: ["always", { allowSingleLineBlocks: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {\n static {\n\nfoo;\n\n} \n}", + options: [{ blocks: "always", classes: "never" }], // "blocks" applies to static blocks + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {\n static {\nfoo;\n} \n}", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {\n static {foo;} \n}", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {\n static\n {\nfoo;\n} \n}", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {\n static\n\n {\nfoo;\n} \n}", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {\n static\n\n {foo;} \n}", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {\n static {// comment\nfoo;\n/* comment */} \n}", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {\n static {\n// comment\nfoo;\n// comment\n} \n}", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {\n static {} static {\n} static {\n\n} \n}", // empty blocks are ignored + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {\n\n static {\nfoo;\n} \n\n}", + options: [{ blocks: "never", classes: "always" }], // "blocks" applies to static blocks + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {\n\n static {\nfoo;\n} static {\n\nfoo;\n\n} \n\n}", + options: [{ classes: "always" }], // if there's no "blocks" in the object option, static blocks are ignored + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {\n static {\nfoo;\n} static {\n\nfoo;\n\n} \n}", + options: [{ classes: "never" }], // if there's no "blocks" in the object option, static blocks are ignored + parserOptions: { ecmaVersion: 2022 } + } ], invalid: [ @@ -748,6 +853,177 @@ ruleTester.run("padded-blocks", rule, { output: "function foo() { /* a\n */ /* b\n */\n\n bar;\n\n/* c\n *//* d\n */}", options: ["always"], errors: [{ messageId: "alwaysPadBlock" }, { messageId: "alwaysPadBlock" }] + }, + { + code: "class A{\nfoo;\n}", + output: "class A{\n\nfoo;\n\n}", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "alwaysPadBlock" }, { messageId: "alwaysPadBlock" }] + }, + { + code: "class A{\n\nfoo;\n\n}", + output: "class A{\nfoo;\n}", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "neverPadBlock" }, { messageId: "neverPadBlock" }] + }, + + // class static blocks + { + code: "class C {\n\n static {\nfoo;\n\n} \n\n}", + output: "class C {\n\n static {\n\nfoo;\n\n} \n\n}", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "alwaysPadBlock" }] + }, + { + code: "class C {\n\n static\n {\nfoo;\n\n} \n\n}", + output: "class C {\n\n static\n {\n\nfoo;\n\n} \n\n}", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "alwaysPadBlock" }] + }, + { + code: "class C {\n\n static\n\n {\nfoo;\n\n} \n\n}", + output: "class C {\n\n static\n\n {\n\nfoo;\n\n} \n\n}", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "alwaysPadBlock" }] + }, + { + code: "class C {\n\n static {\n\nfoo;\n} \n\n}", + output: "class C {\n\n static {\n\nfoo;\n\n} \n\n}", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "alwaysPadBlock" }] + }, + { + code: "class C {\n\n static {foo;} \n\n}", + output: "class C {\n\n static {\nfoo;\n} \n\n}", // this is still not padded, the subsequent fix below will add another pair of `\n`. + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "alwaysPadBlock" }, + { messageId: "alwaysPadBlock" } + ] + }, + { + code: "class C {\n\n static {\nfoo;\n} \n\n}", + output: "class C {\n\n static {\n\nfoo;\n\n} \n\n}", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "alwaysPadBlock" }, + { messageId: "alwaysPadBlock" } + ] + }, + { + code: "class C {\n\n static {// comment\nfoo;\n/* comment */} \n\n}", + output: "class C {\n\n static {// comment\n\nfoo;\n\n/* comment */} \n\n}", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "alwaysPadBlock" }, + { messageId: "alwaysPadBlock" } + ] + }, + { + code: "class C {\n\n static {\n// comment\nfoo;\n// comment\n} \n\n}", + output: "class C {\n\n static {\n\n// comment\nfoo;\n// comment\n\n} \n\n}", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "alwaysPadBlock" }, + { messageId: "alwaysPadBlock" } + ] + }, + { + code: "class C {\n\n static {\n// comment\n\nfoo;\n\n// comment\n} \n\n}", + output: "class C {\n\n static {\n\n// comment\n\nfoo;\n\n// comment\n\n} \n\n}", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "alwaysPadBlock" }, + { messageId: "alwaysPadBlock" } + ] + }, + { + code: "class C {\n static {\nfoo;\n} \n}", + output: "class C {\n static {\n\nfoo;\n\n} \n}", + options: [{ blocks: "always", classes: "never" }], // "blocks" applies to static blocks + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "alwaysPadBlock" }, + { messageId: "alwaysPadBlock" } + ] + }, + { + code: "class C {\n static {\n\nfoo;\n} \n}", + output: "class C {\n static {\nfoo;\n} \n}", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "neverPadBlock" }] + }, + { + code: "class C {\n static\n {\n\nfoo;\n} \n}", + output: "class C {\n static\n {\nfoo;\n} \n}", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "neverPadBlock" }] + }, + { + code: "class C {\n static\n\n {\n\nfoo;\n} \n}", + output: "class C {\n static\n\n {\nfoo;\n} \n}", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "neverPadBlock" }] + }, + { + code: "class C {\n static {\nfoo;\n\n} \n}", + output: "class C {\n static {\nfoo;\n} \n}", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "neverPadBlock" }] + }, + { + code: "class C {\n static {\n\nfoo;\n\n} \n}", + output: "class C {\n static {\nfoo;\n} \n}", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "neverPadBlock" }, + { messageId: "neverPadBlock" } + ] + }, + { + code: "class C {\n static {// comment\n\nfoo;\n\n/* comment */} \n}", + output: "class C {\n static {// comment\nfoo;\n/* comment */} \n}", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "neverPadBlock" }, + { messageId: "neverPadBlock" } + ] + }, + { + code: "class C {\n static {\n\n// comment\nfoo;\n// comment\n\n} \n}", + output: "class C {\n static {\n// comment\nfoo;\n// comment\n} \n}", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "neverPadBlock" }, + { messageId: "neverPadBlock" } + ] + }, + { + code: "class C {\n\n static {\n\nfoo;\n\n} \n\n}", + output: "class C {\n\n static {\nfoo;\n} \n\n}", + options: [{ blocks: "never", classes: "always" }], // "blocks" applies to static blocks + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "neverPadBlock" }, + { messageId: "neverPadBlock" } + ] } ] }); diff --git a/eslint/tests/lib/rules/padding-line-between-statements.js b/eslint/tests/lib/rules/padding-line-between-statements.js index 931cdd2..51ddf0e 100644 --- a/eslint/tests/lib/rules/padding-line-between-statements.js +++ b/eslint/tests/lib/rules/padding-line-between-statements.js @@ -2626,6 +2626,114 @@ ruleTester.run("padding-line-between-statements", rule, { options: [ { blankLine: "always", prev: "block-like", next: "block-like" } ] + }, + + // class static blocks + { + code: "class C {\n static {\n let x;\n\n foo();\n }\n }", + options: [ + { blankLine: "always", prev: "let", next: "expression" } + ], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {\n static {\n let x;\n foo();\n }\n }", + options: [ + { blankLine: "never", prev: "let", next: "expression" } + ], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {\n static {\n let x;\n foo();\n\n const y = 1;\n }\n }", + options: [ + { blankLine: "always", prev: "expression", next: "const" } + ], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {\n static {\n let x;\n foo();\n const y = 1;\n }\n }", + options: [ + { blankLine: "never", prev: "expression", next: "const" } + ], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {\n static {\n let x;\n foo();\n\n const y = 1;\n const z = 1;\n }\n }", + options: [ + { blankLine: "always", prev: "expression", next: "const" } + ], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {\n static {\n let x;\n foo();\n const y = 1;\n const z = 1;\n }\n }", + options: [ + { blankLine: "never", prev: "expression", next: "const" } + ], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C {\n static {\n let a = 0;\n let b =0;\n\n bar();\n }\n }", + options: [ + { blankLine: "always", prev: ["const", "let", "var"], next: "*" }, + { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"] } + ], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { let x; { let y; } let z; } }", + options: [ + { blankLine: "always", prev: "let", next: "let" } + ], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { method() { let x; } static { let y; } }", + options: [ + { blankLine: "always", prev: "let", next: "let" } + ], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { let y; } method() { let x; } }", + options: [ + { blankLine: "always", prev: "let", next: "let" } + ], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { let x; } static { let y; } }", + options: [ + { blankLine: "always", prev: "let", next: "let" } + ], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "let x; class C { static { let y; } }", + options: [ + { blankLine: "always", prev: "let", next: "let" } + ], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { let x; } } let y;", + options: [ + { blankLine: "always", prev: "let", next: "let" } + ], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { let x; } }", + options: [ + { blankLine: "always", prev: "class", next: "let" } + ], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { 'use strict'; let x; } }", // 'use strict'; is "espression", because class static blocks don't have directives + options: [ + { blankLine: "always", prev: "directive", next: "let" } + ], + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -4976,6 +5084,117 @@ ruleTester.run("padding-line-between-statements", rule, { { messageId: "expectedBlankLine" }, { messageId: "expectedBlankLine" } ] + }, + + // class static blocks + { + code: "class C {\n static {\n let x;\n foo();\n }\n }", + output: "class C {\n static {\n let x;\n\n foo();\n }\n }", + options: [ + { blankLine: "always", prev: "let", next: "expression" } + ], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "expectedBlankLine" }] + }, + { + code: "class C {\n static {\n let x;\n\n foo();\n }\n }", + output: "class C {\n static {\n let x;\n foo();\n }\n }", + options: [ + { blankLine: "never", prev: "let", next: "expression" } + ], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedBlankLine" }] + }, + { + code: "class C {\n static {\n let x;\n foo();\n const y = 1;\n }\n }", + output: "class C {\n static {\n let x;\n foo();\n\n const y = 1;\n }\n }", + options: [ + { blankLine: "always", prev: "expression", next: "const" } + ], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "expectedBlankLine" }] + }, + { + code: "class C {\n static {\n let x;\n foo();\n\n const y = 1;\n }\n }", + output: "class C {\n static {\n let x;\n foo();\n const y = 1;\n }\n }", + options: [ + { blankLine: "never", prev: "expression", next: "const" } + ], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedBlankLine" }] + }, + { + code: "class C {\n static {\n let x;\n foo();\n const y = 1;\n const z = 1;\n }\n }", + output: "class C {\n static {\n let x;\n foo();\n\n const y = 1;\n const z = 1;\n }\n }", + options: [ + { blankLine: "always", prev: "expression", next: "const" } + ], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "expectedBlankLine" }] + }, + { + code: "class C {\n static {\n let x;\n foo();\n\n const y = 1;\n const z = 1;\n }\n }", + output: "class C {\n static {\n let x;\n foo();\n const y = 1;\n const z = 1;\n }\n }", + options: [ + { blankLine: "never", prev: "expression", next: "const" } + ], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unexpectedBlankLine" }] + }, + { + code: "class C {\n static {\n let a = 0;\n bar();\n }\n }", + output: "class C {\n static {\n let a = 0;\n\n bar();\n }\n }", + options: [ + { blankLine: "always", prev: ["const", "let", "var"], next: "*" }, + { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"] } + ], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "expectedBlankLine" }] + }, + { + code: "class C { static { let x; { let y; let z; } let q; } }", + output: "class C { static { let x; { let y;\n\n let z; } let q; } }", + options: [ + { blankLine: "always", prev: "let", next: "let" } + ], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "expectedBlankLine" }] + }, + { + code: "class C { static { { let x; } let y; let z; } }", + output: "class C { static { { let x; } let y;\n\n let z; } }", + options: [ + { blankLine: "always", prev: "let", next: "let" } + ], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "expectedBlankLine" }] + }, + { + code: "class C { static { foo(); if (bar) {} } }", + output: "class C { static { foo();\n\n if (bar) {} } }", + options: [ + { blankLine: "always", prev: "expression", next: "block-like" } + ], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "expectedBlankLine" }] + }, + { + code: "class C { static { let x; } } let y;", + output: "class C { static { let x; } }\n\n let y;", + options: [ + { blankLine: "always", prev: "class", next: "let" } + ], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "expectedBlankLine" }] + }, + { + code: "class C { static { 'use strict'; let x; } }", // 'use strict'; is "espression", because class static blocks don't have directives + output: "class C { static { 'use strict';\n\n let x; } }", + options: [ + { blankLine: "always", prev: "expression", next: "let" } + ], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "expectedBlankLine" }] } ] }); diff --git a/eslint/tests/lib/rules/prefer-const.js b/eslint/tests/lib/rules/prefer-const.js index c591d23..0a44b0b 100644 --- a/eslint/tests/lib/rules/prefer-const.js +++ b/eslint/tests/lib/rules/prefer-const.js @@ -173,7 +173,56 @@ ruleTester.run("prefer-const", rule, { // https://github.com/eslint/eslint/issues/10520 "const x = [1,2]; let y; [,y] = x; y = 0;", - "const x = [1,2,3]; let y, z; [y,,z] = x; y = 0; z = 0;" + "const x = [1,2,3]; let y, z; [y,,z] = x; y = 0; z = 0;", + + { + code: "class C { static { let a = 1; a = 2; } }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { let a; a = 1; a = 2; } }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "let a; class C { static { a = 1; } }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { let a; if (foo) { a = 1; } } }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { let a; if (foo) a = 1; } }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { let a, b; if (foo) { ({ a, b } = foo); } } }", + output: null, + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "useConst", data: { name: "a" }, type: "Identifier" }, + { messageId: "useConst", data: { name: "b" }, type: "Identifier" } + ] + }, + { + code: "class C { static { let a, b; if (foo) ({ a, b } = foo); } }", + output: null, + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "useConst", data: { name: "a" }, type: "Identifier" }, + { messageId: "useConst", data: { name: "b" }, type: "Identifier" } + ] + }, + { + code: "class C { static { a; } } let a = 1; ", + options: [{ ignoreReadBeforeAssign: true }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { () => a; let a = 1; } };", + options: [{ ignoreReadBeforeAssign: true }], + parserOptions: { ecmaVersion: 2022 } + } ], invalid: [ { @@ -555,6 +604,86 @@ ruleTester.run("prefer-const", rule, { code: "/*eslint no-undef-init:error*/ let foo = undefined;", output: "/*eslint no-undef-init:error*/ const foo = undefined;", errors: 2 + }, + + { + code: "let a = 1; class C { static { a; } }", + output: "const a = 1; class C { static { a; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "useConst", data: { name: "a" }, type: "Identifier" }] + }, + { + + // this is a TDZ error with either `let` or `const`, but that isn't a concern of this rule + code: "class C { static { a; } } let a = 1;", + output: "class C { static { a; } } const a = 1;", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "useConst", data: { name: "a" }, type: "Identifier" }] + }, + { + code: "class C { static { let a = 1; } }", + output: "class C { static { const a = 1; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "useConst", data: { name: "a" }, type: "Identifier" }] + }, + { + code: "class C { static { if (foo) { let a = 1; } } }", + output: "class C { static { if (foo) { const a = 1; } } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "useConst", data: { name: "a" }, type: "Identifier" }] + }, + { + code: "class C { static { let a = 1; if (foo) { a; } } }", + output: "class C { static { const a = 1; if (foo) { a; } } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "useConst", data: { name: "a" }, type: "Identifier" }] + }, + { + code: "class C { static { if (foo) { let a; a = 1; } } }", + output: null, + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "useConst", data: { name: "a" }, type: "Identifier" }] + }, + { + code: "class C { static { let a; a = 1; } }", + output: null, + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "useConst", data: { name: "a" }, type: "Identifier", column: 27 }] + }, + { + code: "class C { static { let { a, b } = foo; } }", + output: "class C { static { const { a, b } = foo; } }", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "useConst", data: { name: "a" }, type: "Identifier" }, + { messageId: "useConst", data: { name: "b" }, type: "Identifier" } + ] + }, + { + code: "class C { static { let a, b; ({ a, b } = foo); } }", + output: null, + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "useConst", data: { name: "a" }, type: "Identifier" }, + { messageId: "useConst", data: { name: "b" }, type: "Identifier" } + ] + }, + { + code: "class C { static { let a; let b; ({ a, b } = foo); } }", + output: null, + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "useConst", data: { name: "a" }, type: "Identifier" }, + { messageId: "useConst", data: { name: "b" }, type: "Identifier" } + ] + }, + { + code: "class C { static { let a; a = 0; console.log(a); } }", + output: null, + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "useConst", data: { name: "a" }, type: "Identifier" } + ] } ] }); diff --git a/eslint/tests/lib/rules/prefer-destructuring.js b/eslint/tests/lib/rules/prefer-destructuring.js index 8f111a1..7f808a6 100644 --- a/eslint/tests/lib/rules/prefer-destructuring.js +++ b/eslint/tests/lib/rules/prefer-destructuring.js @@ -16,7 +16,7 @@ const rule = require("../../../lib/rules/prefer-destructuring"), // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); ruleTester.run("prefer-destructuring", rule, { valid: [ @@ -157,7 +157,40 @@ ruleTester.run("prefer-destructuring", rule, { // Optional chaining "var foo = array?.[0];", // because the fixed code can throw TypeError. - "var foo = object?.foo;" + "var foo = object?.foo;", + + // Private identifiers + "class C { #x; foo() { const x = this.#x; } }", + "class C { #x; foo() { x = this.#x; } }", + "class C { #x; foo(a) { x = a.#x; } }", + { + code: "class C { #x; foo() { const x = this.#x; } }", + options: [{ array: true, object: true }, { enforceForRenamedProperties: true }] + }, + { + code: "class C { #x; foo() { const y = this.#x; } }", + options: [{ array: true, object: true }, { enforceForRenamedProperties: true }] + }, + { + code: "class C { #x; foo() { x = this.#x; } }", + options: [{ array: true, object: true }, { enforceForRenamedProperties: true }] + }, + { + code: "class C { #x; foo() { y = this.#x; } }", + options: [{ array: true, object: true }, { enforceForRenamedProperties: true }] + }, + { + code: "class C { #x; foo(a) { x = a.#x; } }", + options: [{ array: true, object: true }, { enforceForRenamedProperties: true }] + }, + { + code: "class C { #x; foo(a) { y = a.#x; } }", + options: [{ array: true, object: true }, { enforceForRenamedProperties: true }] + }, + { + code: "class C { #x; foo() { x = this.a.#x; } }", + options: [{ array: true, object: true }, { enforceForRenamedProperties: true }] + } ], invalid: [ diff --git a/eslint/tests/lib/rules/prefer-exponentiation-operator.js b/eslint/tests/lib/rules/prefer-exponentiation-operator.js index 7bccdd6..2de358e 100644 --- a/eslint/tests/lib/rules/prefer-exponentiation-operator.js +++ b/eslint/tests/lib/rules/prefer-exponentiation-operator.js @@ -40,7 +40,7 @@ function invalid(code, output) { // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); ruleTester.run("prefer-exponentiation-operator", rule, { valid: [ @@ -78,7 +78,9 @@ ruleTester.run("prefer-exponentiation-operator", rule, { globalThis.Math.pow(a, b) `, env: { es2020: true } - } + }, + + "class C { #pow; foo() { Math.#pow(a, b); } }" ], invalid: [ diff --git a/eslint/tests/lib/rules/prefer-numeric-literals.js b/eslint/tests/lib/rules/prefer-numeric-literals.js index 39cc11a..e3cacaa 100644 --- a/eslint/tests/lib/rules/prefer-numeric-literals.js +++ b/eslint/tests/lib/rules/prefer-numeric-literals.js @@ -58,6 +58,10 @@ ruleTester.run("prefer-numeric-literals", rule, { { code: "parseInt(1n, 2);", parserOptions: { ecmaVersion: 2020 } + }, + { + code: "class C { #parseInt; foo() { Number.#parseInt(\"111110111\", 2); } }", + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ diff --git a/eslint/tests/lib/rules/prefer-object-spread.js b/eslint/tests/lib/rules/prefer-object-spread.js index 91bf08a..92bbef4 100644 --- a/eslint/tests/lib/rules/prefer-object-spread.js +++ b/eslint/tests/lib/rules/prefer-object-spread.js @@ -6,10 +6,17 @@ "use strict"; -const rule = require("../../../lib/rules/prefer-object-spread"); +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ +const rule = require("../../../lib/rules/prefer-object-spread"); const { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const parserOptions = { ecmaVersion: 2018, sourceType: "module" @@ -76,6 +83,10 @@ ruleTester.run("prefer-object-spread", rule, { `, env: { es2020: true } }, + { + code: "class C { #assign; foo() { Object.#assign({}, foo); } }", + parserOptions: { ecmaVersion: 2022 } + }, // ignore Object.assign() with > 1 arguments if any of the arguments is an object expression with a getter/setter "Object.assign({ get a() {} }, {})", diff --git a/eslint/tests/lib/rules/prefer-promise-reject-errors.js b/eslint/tests/lib/rules/prefer-promise-reject-errors.js index b31ec33..40428d6 100644 --- a/eslint/tests/lib/rules/prefer-promise-reject-errors.js +++ b/eslint/tests/lib/rules/prefer-promise-reject-errors.js @@ -11,12 +11,11 @@ const rule = require("../../../lib/rules/prefer-promise-reject-errors"); const { RuleTester } = require("../../../lib/rule-tester"); - //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2021 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); ruleTester.run("prefer-promise-reject-errors", rule, { @@ -54,7 +53,11 @@ ruleTester.run("prefer-promise-reject-errors", rule, { "Promise.reject(foo = new Error())", "Promise.reject(foo ||= 5)", "Promise.reject(foo.bar ??= 5)", - "Promise.reject(foo[bar] ??= 5)" + "Promise.reject(foo[bar] ??= 5)", + + // Private fields + "class C { #reject; foo() { Promise.#reject(5); } }", + "class C { #error; foo() { Promise.reject(this.#error); } }" ], invalid: [ diff --git a/eslint/tests/lib/rules/prefer-regex-literals.js b/eslint/tests/lib/rules/prefer-regex-literals.js index 0ddaa8d..ccd88ae 100644 --- a/eslint/tests/lib/rules/prefer-regex-literals.js +++ b/eslint/tests/lib/rules/prefer-regex-literals.js @@ -16,7 +16,7 @@ const { RuleTester } = require("../../../lib/rule-tester"); // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); ruleTester.run("prefer-regex-literals", rule, { valid: [ @@ -124,6 +124,10 @@ ruleTester.run("prefer-regex-literals", rule, { { code: "new globalThis.RegExp('a');", env: { es2017: true } + }, + { + code: "class C { #RegExp; foo() { globalThis.#RegExp('a'); } }", + env: { es2020: true } } ], diff --git a/eslint/tests/lib/rules/prefer-rest-params.js b/eslint/tests/lib/rules/prefer-rest-params.js index 3ab3fc5..c7040ea 100644 --- a/eslint/tests/lib/rules/prefer-rest-params.js +++ b/eslint/tests/lib/rules/prefer-rest-params.js @@ -12,6 +12,10 @@ const rule = require("../../../lib/rules/prefer-rest-params"), { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } }); ruleTester.run("prefer-rest-params", rule, { diff --git a/eslint/tests/lib/rules/prefer-spread.js b/eslint/tests/lib/rules/prefer-spread.js index 7f48d84..99c8c5f 100644 --- a/eslint/tests/lib/rules/prefer-spread.js +++ b/eslint/tests/lib/rules/prefer-spread.js @@ -18,7 +18,7 @@ const { RuleTester } = require("../../../lib/rule-tester"); const errors = [{ messageId: "preferSpread", type: "CallExpression" }]; -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); ruleTester.run("prefer-spread", rule, { valid: [ @@ -43,7 +43,10 @@ ruleTester.run("prefer-spread", rule, { // Optional chaining "(a?.b).c.foo.apply(a?.b.c, args);", - "a?.b.c.foo.apply((a?.b).c, args);" + "a?.b.c.foo.apply((a?.b).c, args);", + + // Private fields + "class C { #apply; foo() { foo.#apply(undefined, args); } }" ], invalid: [ { @@ -115,6 +118,12 @@ ruleTester.run("prefer-spread", rule, { { code: "(a?.b).c.foo.apply((a?.b).c, args);", errors + }, + + // Private fields + { + code: "class C { #foo; foo() { obj.#foo.apply(obj, args); } }", + errors } ] }); diff --git a/eslint/tests/lib/rules/quote-props.js b/eslint/tests/lib/rules/quote-props.js index 5186f87..10f3515 100644 --- a/eslint/tests/lib/rules/quote-props.js +++ b/eslint/tests/lib/rules/quote-props.js @@ -12,6 +12,10 @@ const rule = require("../../../lib/rules/quote-props"), { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester(); ruleTester.run("quote-props", rule, { diff --git a/eslint/tests/lib/rules/quotes.js b/eslint/tests/lib/rules/quotes.js index a7e223a..faf3713 100644 --- a/eslint/tests/lib/rules/quotes.js +++ b/eslint/tests/lib/rules/quotes.js @@ -12,6 +12,10 @@ const rule = require("../../../lib/rules/quotes"), { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester(); ruleTester.run("quotes", rule, { @@ -38,6 +42,8 @@ ruleTester.run("quotes", rule, { { code: "var foo = \"a string containing `backtick` quotes\";", options: ["backtick", { avoidEscape: true }] }, { code: "var foo =

;", options: ["backtick"], parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } }, { code: "var foo =
Hello world
;", options: ["backtick"], parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } }, + { code: "class C { \"f\"; \"m\"() {} }", options: ["double"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { 'f'; 'm'() {} }", options: ["single"], parserOptions: { ecmaVersion: 2022 } }, // Backticks are only okay if they have substitutions, contain a line break, or are tagged { code: "var foo = `back\ntick`;", options: ["single"], parserOptions: { ecmaVersion: 6 } }, @@ -74,7 +80,8 @@ ruleTester.run("quotes", rule, { // `backtick` should not warn property/method names (not computed). { code: "var obj = {\"key0\": 0, 'key1': 1};", options: ["backtick"], parserOptions: { ecmaVersion: 6 } }, { code: "class Foo { 'bar'(){} }", options: ["backtick"], parserOptions: { ecmaVersion: 6 } }, - { code: "class Foo { static ''(){} }", options: ["backtick"], parserOptions: { ecmaVersion: 6 } } + { code: "class Foo { static ''(){} }", options: ["backtick"], parserOptions: { ecmaVersion: 6 } }, + { code: "class C { \"double\"; 'single'; }", options: ["backtick"], parserOptions: { ecmaVersion: 2022 } } ], invalid: [ { @@ -627,6 +634,87 @@ ruleTester.run("quotes", rule, { type: "Literal" } ] + }, + + + // class members + { + code: "class C { 'foo'; }", + output: "class C { \"foo\"; }", + options: ["double"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "wrongQuotes", + data: { description: "doublequote" }, + type: "Literal" + } + ] + }, + { + code: "class C { 'foo'() {} }", + output: "class C { \"foo\"() {} }", + options: ["double"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "wrongQuotes", + data: { description: "doublequote" }, + type: "Literal" + } + ] + }, + { + code: "class C { \"foo\"; }", + output: "class C { 'foo'; }", + options: ["single"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "wrongQuotes", + data: { description: "singlequote" }, + type: "Literal" + } + ] + }, + { + code: "class C { \"foo\"() {} }", + output: "class C { 'foo'() {} }", + options: ["single"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "wrongQuotes", + data: { description: "singlequote" }, + type: "Literal" + } + ] + }, + { + code: "class C { [\"foo\"]; }", + output: "class C { [`foo`]; }", + options: ["backtick"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "wrongQuotes", + data: { description: "backtick" }, + type: "Literal" + } + ] + }, + { + code: "class C { foo = \"foo\"; }", + output: "class C { foo = `foo`; }", + options: ["backtick"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "wrongQuotes", + data: { description: "backtick" }, + type: "Literal" + } + ] } ] }); diff --git a/eslint/tests/lib/rules/radix.js b/eslint/tests/lib/rules/radix.js index ddb8363..437d246 100644 --- a/eslint/tests/lib/rules/radix.js +++ b/eslint/tests/lib/rules/radix.js @@ -12,6 +12,10 @@ const rule = require("../../../lib/rules/radix"), { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester(); ruleTester.run("radix", rule, { @@ -44,6 +48,10 @@ ruleTester.run("radix", rule, { "parseInt", "Number.foo();", "Number[parseInt]();", + { code: "class C { #parseInt; foo() { Number.#parseInt(); } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { #parseInt; foo() { Number.#parseInt(foo); } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { #parseInt; foo() { Number.#parseInt(foo, 'bar'); } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { #parseInt; foo() { Number.#parseInt(foo, 10); } }", options: ["as-needed"], parserOptions: { ecmaVersion: 2022 } }, // Ignores if it's shadowed or disabled. "var parseInt; parseInt();", diff --git a/eslint/tests/lib/rules/require-atomic-updates.js b/eslint/tests/lib/rules/require-atomic-updates.js index bd3738a..53d85b7 100644 --- a/eslint/tests/lib/rules/require-atomic-updates.js +++ b/eslint/tests/lib/rules/require-atomic-updates.js @@ -11,12 +11,11 @@ const rule = require("../../../lib/rules/require-atomic-updates"); const { RuleTester } = require("../../../lib/rule-tester"); - //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022 } }); const VARIABLE_ERROR = { messageId: "nonAtomicUpdate", @@ -25,14 +24,20 @@ const VARIABLE_ERROR = { }; const STATIC_PROPERTY_ERROR = { - messageId: "nonAtomicUpdate", - data: { value: "foo.bar" }, + messageId: "nonAtomicObjectUpdate", + data: { value: "foo.bar", object: "foo" }, type: "AssignmentExpression" }; const COMPUTED_PROPERTY_ERROR = { - messageId: "nonAtomicUpdate", - data: { value: "foo[bar].baz" }, + messageId: "nonAtomicObjectUpdate", + data: { value: "foo[bar].baz", object: "foo" }, + type: "AssignmentExpression" +}; + +const PRIVATE_PROPERTY_ERROR = { + messageId: "nonAtomicObjectUpdate", + data: { value: "foo.#bar", object: "foo" }, type: "AssignmentExpression" }; @@ -209,7 +214,29 @@ ruleTester.run("require-atomic-updates", rule, { await a; b = 1; } - ` + `, + + // allowProperties + { + code: ` + async function a(foo) { + if (foo.bar) { + foo.bar = await something; + } + } + `, + options: [{ allowProperties: true }] + }, + { + code: ` + function* g(foo) { + baz = foo.bar; + yield something; + foo.bar = 1; + } + `, + options: [{ allowProperties: true }] + } ], invalid: [ @@ -269,6 +296,10 @@ ruleTester.run("require-atomic-updates", rule, { code: "const foo = []; async function x() { foo[bar].baz += await result; }", errors: [COMPUTED_PROPERTY_ERROR] }, + { + code: "const foo = {}; class C { #bar; async wrap() { foo.#bar += await baz } }", + errors: [PRIVATE_PROPERTY_ERROR] + }, { code: "let foo; async function* x() { foo = (yield foo) + await bar; }", errors: [VARIABLE_ERROR] @@ -319,6 +350,129 @@ ruleTester.run("require-atomic-updates", rule, { } `, errors: [STATIC_PROPERTY_ERROR] + }, + + // https://github.com/eslint/eslint/issues/15076 + { + code: ` + async () => { + opts.spec = process.stdin; + try { + const { exit_code } = await run(opts); + process.exitCode = exit_code; + } catch (e) { + process.exitCode = 1; + } + }; + `, + env: { node: true }, + errors: [ + { + messageId: "nonAtomicObjectUpdate", + data: { value: "process.exitCode", object: "process" }, + type: "AssignmentExpression", + line: 6 + }, + { + messageId: "nonAtomicObjectUpdate", + data: { value: "process.exitCode", object: "process" }, + type: "AssignmentExpression", + line: 8 + } + ] + }, + + // allowProperties + { + code: ` + async function a(foo) { + if (foo.bar) { + foo.bar = await something; + } + } + `, + errors: [STATIC_PROPERTY_ERROR] + }, + { + code: ` + function* g(foo) { + baz = foo.bar; + yield something; + foo.bar = 1; + } + `, + errors: [STATIC_PROPERTY_ERROR] + }, + { + code: ` + async function a(foo) { + if (foo.bar) { + foo.bar = await something; + } + } + `, + options: [{}], + errors: [STATIC_PROPERTY_ERROR] + + }, + { + code: ` + function* g(foo) { + baz = foo.bar; + yield something; + foo.bar = 1; + } + `, + options: [{}], + errors: [STATIC_PROPERTY_ERROR] + }, + { + code: ` + async function a(foo) { + if (foo.bar) { + foo.bar = await something; + } + } + `, + options: [{ allowProperties: false }], + errors: [STATIC_PROPERTY_ERROR] + + }, + { + code: ` + function* g(foo) { + baz = foo.bar; + yield something; + foo.bar = 1; + } + `, + options: [{ allowProperties: false }], + errors: [STATIC_PROPERTY_ERROR] + }, + { + code: ` + let foo; + async function a() { + if (foo) { + foo = await something; + } + } + `, + options: [{ allowProperties: true }], + errors: [VARIABLE_ERROR] + + }, + { + code: ` + let foo; + function* g() { + baz = foo; + yield something; + foo = 1; + } + `, + options: [{ allowProperties: true }], + errors: [VARIABLE_ERROR] } ] }); diff --git a/eslint/tests/lib/rules/require-jsdoc.js b/eslint/tests/lib/rules/require-jsdoc.js index 603ece2..f2f19da 100644 --- a/eslint/tests/lib/rules/require-jsdoc.js +++ b/eslint/tests/lib/rules/require-jsdoc.js @@ -4,6 +4,10 @@ */ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + const rule = require("../../../lib/rules/require-jsdoc"), { RuleTester } = require("../../../lib/rule-tester"); diff --git a/eslint/tests/lib/rules/require-unicode-regexp.js b/eslint/tests/lib/rules/require-unicode-regexp.js index 6033d44..16b6be4 100644 --- a/eslint/tests/lib/rules/require-unicode-regexp.js +++ b/eslint/tests/lib/rules/require-unicode-regexp.js @@ -40,7 +40,8 @@ ruleTester.run("require-unicode-regexp", rule, { { code: "globalThis.RegExp('foo', 'u')", env: { es2020: true } }, { code: "const flags = 'u'; new globalThis.RegExp('', flags)", env: { es2020: true } }, { code: "const flags = 'g'; new globalThis.RegExp('', flags + 'u')", env: { es2020: true } }, - { code: "const flags = 'gimu'; new globalThis.RegExp('foo', flags[3])", env: { es2020: true } } + { code: "const flags = 'gimu'; new globalThis.RegExp('foo', flags[3])", env: { es2020: true } }, + { code: "class C { #RegExp; foo() { new globalThis.#RegExp('foo') } }", parserOptions: { ecmaVersion: 2022 }, env: { es2020: true } } ], invalid: [ { diff --git a/eslint/tests/lib/rules/rest-spread-spacing.js b/eslint/tests/lib/rules/rest-spread-spacing.js index bcef064..73b4f3d 100644 --- a/eslint/tests/lib/rules/rest-spread-spacing.js +++ b/eslint/tests/lib/rules/rest-spread-spacing.js @@ -12,6 +12,11 @@ const rule = require("../../../lib/rules/rest-spread-spacing"), { RuleTester } = require("../../../lib/rule-tester"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } }); ruleTester.run("rest-spread-spacing", rule, { diff --git a/eslint/tests/lib/rules/semi-spacing.js b/eslint/tests/lib/rules/semi-spacing.js index b01dac0..ffd6785 100644 --- a/eslint/tests/lib/rules/semi-spacing.js +++ b/eslint/tests/lib/rules/semi-spacing.js @@ -57,7 +57,24 @@ ruleTester.run("semi-spacing", rule, { { code: "function foo() { return 2; }", options: [{ after: false }] }, { code: "for ( var i = 0;i < results.length; ) {}", options: [{ after: false }] }, - "do {} while (true); foo" + "do {} while (true); foo", + + // Class fields + { + code: "class C { foo; bar; method() {} }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo }", + parserOptions: { ecmaVersion: 2022 } + }, + + // Empty are ignored (`no-extra-semi` rule will remove those) + "foo; ;;;;;;;;;", + { + code: "class C { foo; ;;;;;;;;;; }", + parserOptions: { ecmaVersion: 2022 } + } ], invalid: [ { @@ -417,6 +434,68 @@ ruleTester.run("semi-spacing", rule, { endLine: 1, endColumn: 22 }] + }, + + // Class fields + { + code: "class C { foo ;bar;}", + output: "class C { foo; bar;}", + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "unexpectedWhitespaceBefore", + type: "PropertyDefinition", + line: 1, + column: 14, + endLine: 1, + endColumn: 15 + }, + { + messageId: "missingWhitespaceAfter", + type: "PropertyDefinition", + line: 1, + column: 15, + endLine: 1, + endColumn: 16 + } + ] + }, + { + code: "class C { foo; bar ; }", + output: "class C { foo ;bar ; }", + options: [{ before: true, after: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "missingWhitespaceBefore", + type: "PropertyDefinition", + line: 1, + column: 14, + endLine: 1, + endColumn: 15 + }, + { + messageId: "unexpectedWhitespaceAfter", + type: "PropertyDefinition", + line: 1, + column: 15, + endLine: 1, + endColumn: 16 + } + ] + }, + { + code: "class C { foo;static {}}", + output: "class C { foo; static {}}", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "missingWhitespaceAfter", + type: "PropertyDefinition", + line: 1, + column: 14, + endLine: 1, + endColumn: 15 + }] } ] }); diff --git a/eslint/tests/lib/rules/semi-style.js b/eslint/tests/lib/rules/semi-style.js index 319a80c..7ab8f22 100644 --- a/eslint/tests/lib/rules/semi-style.js +++ b/eslint/tests/lib/rules/semi-style.js @@ -33,6 +33,8 @@ ruleTester.run("semi-style", rule, { { code: "for(a;b;c);", options: ["last"] }, { code: "for(a;\nb;\nc);", options: ["last"] }, { code: "for((a\n);\n(b\n);\n(c));", options: ["last"] }, + { code: "class C { a; b; }", options: ["last"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C {\na;\nb;\n}", options: ["last"], parserOptions: { ecmaVersion: 2022 } }, { code: "if(a)foo;\nbar", options: ["last"] }, { code: ";", options: ["first"] }, { code: ";foo;bar;baz;", options: ["first"] }, @@ -40,6 +42,8 @@ ruleTester.run("semi-style", rule, { { code: "for(a;b;c);", options: ["first"] }, { code: "for(a;\nb;\nc);", options: ["first"] }, { code: "for((a\n);\n(b\n);\n(c));", options: ["first"] }, + { code: "class C { a ;b }", options: ["first"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C {\na\n;b\n}", options: ["first"], parserOptions: { ecmaVersion: 2022 } }, // edge cases { @@ -123,6 +127,202 @@ ruleTester.run("semi-style", rule, { while (a) `, options: ["last"] + }, + + // Class static blocks + { + code: ` + class C { + static {} + } + `, + options: ["last"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: ` + class C { + static { + foo + } + } + `, + options: ["last"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: ` + class C { + static { + foo + bar + } + } + `, + options: ["last"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: ` + class C { + static { + ; + } + } + `, + options: ["last"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: ` + class C { + static { + foo; + } + } + `, + options: ["last"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: ` + class C { + static { + foo; + bar; + } + } + `, + options: ["last"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: ` + class C { + static { + foo; + bar; + baz; + } + } + `, + options: ["last"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: ` + class C { + static {} + } + `, + options: ["first"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: ` + class C { + static { + foo + } + } + `, + options: ["first"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: ` + class C { + static { + foo + bar + } + } + `, + options: ["first"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: ` + class C { + static { + ; + } + } + `, + options: ["first"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: ` + class C { + static { + ;foo + } + } + `, + options: ["first"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: ` + class C { + static { + foo; + } + } + `, + options: ["first"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: ` + class C { + static { + foo + ;bar + } + } + `, + options: ["first"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: ` + class C { + static { + foo + ;bar; + } + } + `, + options: ["first"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: ` + class C { + static { + foo + ;bar + ;baz + } + } + `, + options: ["first"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: ` + class C { + static { + foo + ;bar + ;baz; + } + } + `, + options: ["first"], + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -384,6 +584,82 @@ ruleTester.run("semi-style", rule, { pos: "the beginning of the next line" } }] + }, + + // Class fields + { + code: "class C { foo\n;bar }", + output: "class C { foo;\nbar }", + options: ["last"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "expectedSemiColon", + data: { + pos: "the end of the previous line" + } + }] + }, + { + code: "class C { foo;\nbar }", + output: "class C { foo\n;bar }", + options: ["first"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "expectedSemiColon", + data: { + pos: "the beginning of the next line" + } + }] + }, + + // Class static blocks + { + code: "class C { static { foo\n; } }", + output: "class C { static { foo;\n} }", + options: ["last"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "expectedSemiColon", + data: { + pos: "the end of the previous line" + } + }] + }, + { + code: "class C { static { foo\n ;bar } }", + output: "class C { static { foo;\nbar } }", + options: ["last"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "expectedSemiColon", + data: { + pos: "the end of the previous line" + } + }] + }, + { + code: "class C { static { foo;\nbar\n ; } }", + output: "class C { static { foo;\nbar;\n} }", + options: ["last"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "expectedSemiColon", + data: { + pos: "the end of the previous line" + } + }] + }, + { + code: "class C { static { foo;\nbar } }", + output: "class C { static { foo\n;bar } }", + options: ["first"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "expectedSemiColon", + data: { + pos: "the beginning of the next line" + } + }] } ] }); diff --git a/eslint/tests/lib/rules/semi.js b/eslint/tests/lib/rules/semi.js index ce6310e..d873821 100644 --- a/eslint/tests/lib/rules/semi.js +++ b/eslint/tests/lib/rules/semi.js @@ -12,6 +12,10 @@ const rule = require("../../../lib/rules/semi"), { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester(); ruleTester.run("semi", rule, { @@ -44,14 +48,73 @@ ruleTester.run("semi", rule, { { code: "for (let thing of {}) {\n console.log(thing);\n}", parserOptions: { ecmaVersion: 6 } }, { code: "do{}while(true)", options: ["never"] }, { code: "do{}while(true);", options: ["always"] }, + { code: "class C { static {} }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static {} }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo(); } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo(); } }", options: ["always"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo(); bar(); } }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo(); bar(); baz();} }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo() } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo()\nbar() } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo()\nbar()\nbaz() } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo(); bar() } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo();\n (a) } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo()\n ;(a) } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo();\n [a] } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo()\n ;[a] } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo();\n +a } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo()\n ;+a } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo();\n -a } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo()\n ;-a } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo();\n /a/ } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo()\n ;/a/} }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { + code: "class C { static { foo();\n (a) } }", + options: ["never", { beforeStatementContinuationChars: "never" }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { do ; while (foo)\n (a)} }", + options: ["never", { beforeStatementContinuationChars: "never" }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { do ; while (foo)\n ;(a)} }", + options: ["never", { beforeStatementContinuationChars: "always" }], + parserOptions: { ecmaVersion: 2022 } + }, + // omitLastInOneLineBlock: true { code: "if (foo) { bar() }", options: ["always", { omitLastInOneLineBlock: true }] }, { code: "if (foo) { bar(); baz() }", options: ["always", { omitLastInOneLineBlock: true }] }, + { code: "if (foo)\n{ bar(); baz() }", options: ["always", { omitLastInOneLineBlock: true }] }, + { code: "if (foo) {\n bar(); baz(); }", options: ["always", { omitLastInOneLineBlock: true }] }, + { code: "if (foo) { bar(); baz(); \n}", options: ["always", { omitLastInOneLineBlock: true }] }, + { code: "function foo() { bar(); baz() }", options: ["always", { omitLastInOneLineBlock: true }] }, + { code: "function foo()\n{ bar(); baz() }", options: ["always", { omitLastInOneLineBlock: true }] }, + { code: "function foo(){\n bar(); baz(); }", options: ["always", { omitLastInOneLineBlock: true }] }, + { code: "function foo(){ bar(); baz(); \n}", options: ["always", { omitLastInOneLineBlock: true }] }, + { code: "() => { bar(); baz() };", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "() =>\n { bar(); baz() };", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "() => {\n bar(); baz(); };", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "() => { bar(); baz(); \n};", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const obj = { method() { bar(); baz() } };", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const obj = { method()\n { bar(); baz() } };", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const obj = { method() {\n bar(); baz(); } };", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "const obj = { method() { bar(); baz(); \n} };", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "class C {\n method() { bar(); baz() } \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "class C {\n method()\n { bar(); baz() } \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "class C {\n method() {\n bar(); baz(); } \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "class C {\n method() { bar(); baz(); \n} \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 6 } }, + { code: "class C {\n static { bar(); baz() } \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C {\n static\n { bar(); baz() } \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C {\n static {\n bar(); baz(); } \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C {\n static { bar(); baz(); \n} \n}", options: ["always", { omitLastInOneLineBlock: true }], parserOptions: { ecmaVersion: 2022 } }, - - // method definitions don't have a semicolon. + // method definitions and static blocks don't have a semicolon. { code: "class A { a() {} b() {} }", parserOptions: { ecmaVersion: 6 } }, { code: "var A = class { a() {} b() {} };", parserOptions: { ecmaVersion: 6 } }, + { code: "class A { static {} }", parserOptions: { ecmaVersion: 2022 } }, { code: "import theDefault, { named1, named2 } from 'src/mylib';", parserOptions: { ecmaVersion: 6, sourceType: "module" } }, { code: "import theDefault, { named1, named2 } from 'src/mylib'", options: ["never"], parserOptions: { ecmaVersion: 6, sourceType: "module" } }, @@ -230,6 +293,214 @@ ruleTester.run("semi", rule, { `, options: ["never", { beforeStatementContinuationChars: "never" }], parserOptions: { ecmaVersion: 2015 } + }, + + // Class fields + { + code: "class C { foo; }", + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo = obj\n;[bar] }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo;\n[bar]; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo\n;[bar] }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo\n[bar] }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo\n;[bar] }", + options: ["never", { beforeStatementContinuationChars: "always" }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo\n[bar] }", + options: ["never", { beforeStatementContinuationChars: "never" }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo = () => {}\n;[bar] }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo = () => {}\n[bar] }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo = () => {}\n;[bar] }", + options: ["never", { beforeStatementContinuationChars: "always" }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo = () => {}\n[bar] }", + options: ["never", { beforeStatementContinuationChars: "never" }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo() {} }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo() {}; }", // no-extra-semi reports it + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static {}; }", // no-extra-semi reports it + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { a=b;\n*foo() {} }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { get;\nfoo() {} }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { set;\nfoo() {} }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static;\nfoo() {} }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { a=b;\nin }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { a=b;\ninstanceof }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: ` + class C { + x + [foo] + + x; + [foo] + + x = "a"; + [foo] + } + `, + options: ["never", { beforeStatementContinuationChars: "never" }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: ` + class C { + x + [foo] + + x; + [foo] + + x = 1; + [foo] + } + `, + options: ["never", { beforeStatementContinuationChars: "always" }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo\n[bar] }", + options: ["never", { beforeStatementContinuationChars: "always" }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo = () => {}\n[bar] }", + options: ["never", { beforeStatementContinuationChars: "always" }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo\n;[bar] }", + options: ["never", { beforeStatementContinuationChars: "never" }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { foo = () => {}\n;[bar] }", + options: ["never", { beforeStatementContinuationChars: "never" }], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { [foo] = bar;\nin }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #foo = bar;\nin }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static static = bar;\nin }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { [foo];\nin }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { [get];\nin }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { [get] = 5;\nin }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #get;\nin }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { #set = 5;\nin }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static static;\nin }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } } ], invalid: [ @@ -240,6 +511,7 @@ ruleTester.run("semi", rule, { errors: [{ messageId: "missingSemi", type: "ImportDeclaration", + line: 1, column: 33, endLine: void 0, endColumn: void 0 @@ -251,7 +523,11 @@ ruleTester.run("semi", rule, { parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ messageId: "missingSemi", - type: "ImportDeclaration" + type: "ImportDeclaration", + line: 1, + column: 35, + endLine: void 0, + endColumn: void 0 }] }, { @@ -260,7 +536,11 @@ ruleTester.run("semi", rule, { parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ messageId: "missingSemi", - type: "ImportDeclaration" + type: "ImportDeclaration", + line: 1, + column: 37, + endLine: void 0, + endColumn: void 0 }] }, { @@ -269,7 +549,11 @@ ruleTester.run("semi", rule, { parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ messageId: "missingSemi", - type: "ImportDeclaration" + type: "ImportDeclaration", + line: 1, + column: 19, + endLine: void 0, + endColumn: void 0 }] }, { @@ -278,7 +562,11 @@ ruleTester.run("semi", rule, { parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ messageId: "missingSemi", - type: "ImportDeclaration" + type: "ImportDeclaration", + line: 1, + column: 55, + endLine: void 0, + endColumn: void 0 }] }, { @@ -286,7 +574,11 @@ ruleTester.run("semi", rule, { output: "function foo() { return []; }", errors: [{ messageId: "missingSemi", - type: "ReturnStatement" + type: "ReturnStatement", + line: 1, + column: 27, + endLine: 1, + endColumn: 28 }] }, { @@ -294,7 +586,11 @@ ruleTester.run("semi", rule, { output: "while(true) { break; }", errors: [{ messageId: "missingSemi", - type: "BreakStatement" + type: "BreakStatement", + line: 1, + column: 20, + endLine: 1, + endColumn: 21 }] }, { @@ -302,7 +598,11 @@ ruleTester.run("semi", rule, { output: "while(true) { continue; }", errors: [{ messageId: "missingSemi", - type: "ContinueStatement" + type: "ContinueStatement", + line: 1, + column: 23, + endLine: 1, + endColumn: 24 }] }, { @@ -311,7 +611,11 @@ ruleTester.run("semi", rule, { parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "missingSemi", - type: "VariableDeclaration" + type: "VariableDeclaration", + line: 1, + column: 10, + endLine: void 0, + endColumn: void 0 }] }, { @@ -319,7 +623,11 @@ ruleTester.run("semi", rule, { output: "var x = 5;", errors: [{ messageId: "missingSemi", - type: "VariableDeclaration" + type: "VariableDeclaration", + line: 1, + column: 10, + endLine: void 0, + endColumn: void 0 }] }, { @@ -327,7 +635,11 @@ ruleTester.run("semi", rule, { output: "var x = 5, y;", errors: [{ messageId: "missingSemi", - type: "VariableDeclaration" + type: "VariableDeclaration", + line: 1, + column: 13, + endLine: void 0, + endColumn: void 0 }] }, { @@ -335,7 +647,11 @@ ruleTester.run("semi", rule, { output: "debugger;", errors: [{ messageId: "missingSemi", - type: "DebuggerStatement" + type: "DebuggerStatement", + line: 1, + column: 9, + endLine: void 0, + endColumn: void 0 }] }, { @@ -344,7 +660,9 @@ ruleTester.run("semi", rule, { errors: [{ messageId: "missingSemi", type: "ExpressionStatement", + line: 1, column: 6, + endLine: void 0, endColumn: void 0 }] }, @@ -354,6 +672,7 @@ ruleTester.run("semi", rule, { errors: [{ messageId: "missingSemi", type: "ExpressionStatement", + line: 1, column: 6, endLine: 2, endColumn: 1 @@ -365,6 +684,7 @@ ruleTester.run("semi", rule, { errors: [{ messageId: "missingSemi", type: "ExpressionStatement", + line: 1, column: 6, endLine: 2, endColumn: 1 @@ -376,6 +696,7 @@ ruleTester.run("semi", rule, { errors: [{ messageId: "missingSemi", type: "ExpressionStatement", + line: 1, column: 6, endLine: 2, endColumn: 1 @@ -387,6 +708,7 @@ ruleTester.run("semi", rule, { errors: [{ messageId: "missingSemi", type: "ExpressionStatement", + line: 1, column: 6, endLine: 2, endColumn: 1 @@ -397,7 +719,11 @@ ruleTester.run("semi", rule, { output: "for (var a in b) var i; ", errors: [{ messageId: "missingSemi", - type: "VariableDeclaration" + type: "VariableDeclaration", + line: 1, + column: 23, + endLine: 1, + endColumn: 24 }] }, { @@ -405,7 +731,11 @@ ruleTester.run("semi", rule, { output: "for (;;){var i;}", errors: [{ messageId: "missingSemi", - type: "VariableDeclaration" + type: "VariableDeclaration", + line: 1, + column: 15, + endLine: 1, + endColumn: 16 }] }, { @@ -413,7 +743,11 @@ ruleTester.run("semi", rule, { output: "for (;;) var i; ", errors: [{ messageId: "missingSemi", - type: "VariableDeclaration" + type: "VariableDeclaration", + line: 1, + column: 15, + endLine: 1, + endColumn: 16 }] }, { @@ -421,7 +755,11 @@ ruleTester.run("semi", rule, { output: "for (var j;;) {var i;}", errors: [{ messageId: "missingSemi", - type: "VariableDeclaration" + type: "VariableDeclaration", + line: 1, + column: 21, + endLine: 1, + endColumn: 22 }] }, { @@ -430,7 +768,10 @@ ruleTester.run("semi", rule, { errors: [{ messageId: "missingSemi", type: "VariableDeclaration", - line: 3 + line: 3, + column: 2, + endLine: void 0, + endColumn: void 0 }] }, { @@ -439,7 +780,10 @@ ruleTester.run("semi", rule, { errors: [{ messageId: "missingSemi", type: "VariableDeclaration", - line: 1 + line: 1, + column: 8, + endLine: 2, + endColumn: 1 }] }, { @@ -448,7 +792,10 @@ ruleTester.run("semi", rule, { errors: [{ messageId: "missingSemi", type: "ThrowStatement", - line: 1 + line: 1, + column: 23, + endLine: void 0, + endColumn: void 0 }] }, { @@ -457,7 +804,10 @@ ruleTester.run("semi", rule, { errors: [{ messageId: "missingSemi", type: "DoWhileStatement", - line: 1 + line: 1, + column: 16, + endLine: void 0, + endColumn: void 0 }] }, { @@ -465,7 +815,9 @@ ruleTester.run("semi", rule, { output: "if (foo) {bar();}", errors: [{ messageId: "missingSemi", + line: 1, column: 16, + endLine: 1, endColumn: 17 }] }, @@ -474,7 +826,9 @@ ruleTester.run("semi", rule, { output: "if (foo) {bar();} ", errors: [{ messageId: "missingSemi", + line: 1, column: 16, + endLine: 1, endColumn: 17 }] }, @@ -483,6 +837,7 @@ ruleTester.run("semi", rule, { output: "if (foo) {bar();\n}", errors: [{ messageId: "missingSemi", + line: 1, column: 16, endLine: 2, endColumn: 1 @@ -496,7 +851,10 @@ ruleTester.run("semi", rule, { errors: [{ messageId: "extraSemi", type: "ThrowStatement", - column: 23 + line: 1, + column: 23, + endLine: 1, + endColumn: 24 }] }, { @@ -505,7 +863,11 @@ ruleTester.run("semi", rule, { options: ["never"], errors: [{ messageId: "extraSemi", - type: "ReturnStatement" + type: "ReturnStatement", + line: 1, + column: 27, + endLine: 1, + endColumn: 28 }] }, { @@ -514,7 +876,11 @@ ruleTester.run("semi", rule, { options: ["never"], errors: [{ messageId: "extraSemi", - type: "BreakStatement" + type: "BreakStatement", + line: 1, + column: 20, + endLine: 1, + endColumn: 21 }] }, { @@ -523,7 +889,11 @@ ruleTester.run("semi", rule, { options: ["never"], errors: [{ messageId: "extraSemi", - type: "ContinueStatement" + type: "ContinueStatement", + line: 1, + column: 23, + endLine: 1, + endColumn: 24 }] }, { @@ -533,7 +903,11 @@ ruleTester.run("semi", rule, { parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "extraSemi", - type: "VariableDeclaration" + type: "VariableDeclaration", + line: 1, + column: 10, + endLine: 1, + endColumn: 11 }] }, { @@ -542,7 +916,11 @@ ruleTester.run("semi", rule, { options: ["never"], errors: [{ messageId: "extraSemi", - type: "VariableDeclaration" + type: "VariableDeclaration", + line: 1, + column: 10, + endLine: 1, + endColumn: 11 }] }, { @@ -551,7 +929,11 @@ ruleTester.run("semi", rule, { options: ["never"], errors: [{ messageId: "extraSemi", - type: "VariableDeclaration" + type: "VariableDeclaration", + line: 1, + column: 13, + endLine: 1, + endColumn: 14 }] }, { @@ -560,7 +942,11 @@ ruleTester.run("semi", rule, { options: ["never"], errors: [{ messageId: "extraSemi", - type: "DebuggerStatement" + type: "DebuggerStatement", + line: 1, + column: 9, + endLine: 1, + endColumn: 10 }] }, { @@ -569,7 +955,11 @@ ruleTester.run("semi", rule, { options: ["never"], errors: [{ messageId: "extraSemi", - type: "ExpressionStatement" + type: "ExpressionStatement", + line: 1, + column: 6, + endLine: 1, + endColumn: 7 }] }, { @@ -578,7 +968,11 @@ ruleTester.run("semi", rule, { options: ["never"], errors: [{ messageId: "extraSemi", - type: "VariableDeclaration" + type: "VariableDeclaration", + line: 1, + column: 23, + endLine: 1, + endColumn: 24 }] }, { @@ -587,7 +981,11 @@ ruleTester.run("semi", rule, { options: ["never"], errors: [{ messageId: "extraSemi", - type: "VariableDeclaration" + type: "VariableDeclaration", + line: 1, + column: 15, + endLine: 1, + endColumn: 16 }] }, { @@ -596,7 +994,11 @@ ruleTester.run("semi", rule, { options: ["never"], errors: [{ messageId: "extraSemi", - type: "VariableDeclaration" + type: "VariableDeclaration", + line: 1, + column: 15, + endLine: 1, + endColumn: 16 }] }, { @@ -605,7 +1007,11 @@ ruleTester.run("semi", rule, { options: ["never"], errors: [{ messageId: "extraSemi", - type: "VariableDeclaration" + type: "VariableDeclaration", + line: 1, + column: 21, + endLine: 1, + endColumn: 22 }] }, { @@ -615,7 +1021,10 @@ ruleTester.run("semi", rule, { errors: [{ messageId: "extraSemi", type: "VariableDeclaration", - line: 3 + line: 3, + column: 2, + endLine: 3, + endColumn: 3 }] }, { @@ -625,7 +1034,11 @@ ruleTester.run("semi", rule, { parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ messageId: "extraSemi", - type: "ImportDeclaration" + type: "ImportDeclaration", + line: 1, + column: 55, + endLine: 1, + endColumn: 56 }] }, { @@ -635,106 +1048,456 @@ ruleTester.run("semi", rule, { errors: [{ messageId: "extraSemi", type: "DoWhileStatement", - line: 1 + line: 1, + column: 16, + endLine: 1, + endColumn: 17 }] }, - { - code: "if (foo) { bar()\n }", - output: "if (foo) { bar();\n }", - options: ["always", { omitLastInOneLineBlock: true }], + code: "class C { static { foo() } }", + output: "class C { static { foo(); } }", + parserOptions: { ecmaVersion: 2022 }, errors: [{ - messageId: "missingSemi" + messageId: "missingSemi", + type: "ExpressionStatement", + line: 1, + column: 25, + endLine: 1, + endColumn: 26 }] }, { - code: "if (foo) {\n bar() }", - output: "if (foo) {\n bar(); }", - options: ["always", { omitLastInOneLineBlock: true }], + code: "class C { static { foo() } }", + output: "class C { static { foo(); } }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, errors: [{ - messageId: "missingSemi" + messageId: "missingSemi", + type: "ExpressionStatement", + line: 1, + column: 25, + endLine: 1, + endColumn: 26 }] }, { - code: "if (foo) {\n bar(); baz() }", - output: "if (foo) {\n bar(); baz(); }", - options: ["always", { omitLastInOneLineBlock: true }], + code: "class C { static { foo(); bar() } }", + output: "class C { static { foo(); bar(); } }", + parserOptions: { ecmaVersion: 2022 }, errors: [{ - messageId: "missingSemi" + messageId: "missingSemi", + type: "ExpressionStatement", + line: 1, + column: 32, + endLine: 1, + endColumn: 33 }] }, { - code: "if (foo) { bar(); }", - output: "if (foo) { bar() }", - options: ["always", { omitLastInOneLineBlock: true }], + code: "class C { static { foo()\nbar(); } }", + output: "class C { static { foo();\nbar(); } }", + parserOptions: { ecmaVersion: 2022 }, errors: [{ - messageId: "extraSemi" + messageId: "missingSemi", + type: "ExpressionStatement", + line: 1, + column: 25, + endLine: 2, + endColumn: 1 }] }, - - - // exports, "always" { - code: "export * from 'foo'", - output: "export * from 'foo';", - parserOptions: { ecmaVersion: 6, sourceType: "module" }, + code: "class C { static { foo(); bar()\nbaz(); } }", + output: "class C { static { foo(); bar();\nbaz(); } }", + parserOptions: { ecmaVersion: 2022 }, errors: [{ messageId: "missingSemi", - type: "ExportAllDeclaration" + type: "ExpressionStatement", + line: 1, + column: 32, + endLine: 2, + endColumn: 1 }] }, { - code: "export { foo } from 'foo'", - output: "export { foo } from 'foo';", - parserOptions: { ecmaVersion: 6, sourceType: "module" }, + code: "class C { static { foo(); } }", + output: "class C { static { foo() } }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, errors: [{ - messageId: "missingSemi", - type: "ExportNamedDeclaration" + messageId: "extraSemi", + type: "ExpressionStatement", + line: 1, + column: 25, + endLine: 1, + endColumn: 26 }] }, { - code: "var foo = 0;export { foo }", - output: "var foo = 0;export { foo };", - parserOptions: { ecmaVersion: 6, sourceType: "module" }, + code: "class C { static { foo();\nbar() } }", + output: "class C { static { foo()\nbar() } }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, errors: [{ - messageId: "missingSemi", - type: "ExportNamedDeclaration" + messageId: "extraSemi", + type: "ExpressionStatement", + line: 1, + column: 25, + endLine: 1, + endColumn: 26 }] }, { - code: "export var foo", - output: "export var foo;", - parserOptions: { ecmaVersion: 6, sourceType: "module" }, + code: "class C { static { foo()\nbar(); } }", + output: "class C { static { foo()\nbar() } }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, errors: [{ - messageId: "missingSemi", - type: "VariableDeclaration" + messageId: "extraSemi", + type: "ExpressionStatement", + line: 2, + column: 6, + endLine: 2, + endColumn: 7 }] }, { - code: "export let foo", - output: "export let foo;", - parserOptions: { ecmaVersion: 6, sourceType: "module" }, + code: "class C { static { foo()\nbar();\nbaz() } }", + output: "class C { static { foo()\nbar()\nbaz() } }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, errors: [{ - messageId: "missingSemi", - type: "VariableDeclaration" + messageId: "extraSemi", + type: "ExpressionStatement", + line: 2, + column: 6, + endLine: 2, + endColumn: 7 }] }, { - code: "export const FOO = 42", - output: "export const FOO = 42;", - parserOptions: { ecmaVersion: 6, sourceType: "module" }, + code: "class C { static { do ; while (foo)\n (a)} }", + output: "class C { static { do ; while (foo);\n (a)} }", + options: ["never", { beforeStatementContinuationChars: "always" }], + parserOptions: { ecmaVersion: 2022 }, errors: [{ messageId: "missingSemi", - type: "VariableDeclaration" - }] - }, + type: "DoWhileStatement", + line: 1, + column: 36, + endLine: 2, + endColumn: 1 + }] + }, + { + code: "class C { static { do ; while (foo)\n ;(a)} }", + output: "class C { static { do ; while (foo)\n (a)} }", + options: ["never", { beforeStatementContinuationChars: "never" }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "extraSemi", + type: "DoWhileStatement", + line: 2, + column: 2, + endLine: 2, + endColumn: 3 + }] + }, + + // omitLastInOneLineBlock: true + { + code: "if (foo) { bar()\n }", + output: "if (foo) { bar();\n }", + options: ["always", { omitLastInOneLineBlock: true }], + errors: [{ + messageId: "missingSemi", + line: 1, + column: 17, + endLine: 2, + endColumn: 1 + }] + }, + { + code: "if (foo) {\n bar() }", + output: "if (foo) {\n bar(); }", + options: ["always", { omitLastInOneLineBlock: true }], + errors: [{ + messageId: "missingSemi", + line: 2, + column: 7, + endLine: 2, + endColumn: 8 + }] + }, + { + code: "if (foo) {\n bar(); baz() }", + output: "if (foo) {\n bar(); baz(); }", + options: ["always", { omitLastInOneLineBlock: true }], + errors: [{ + messageId: "missingSemi", + line: 2, + column: 14, + endLine: 2, + endColumn: 15 + }] + }, + { + code: "if (foo) { bar(); }", + output: "if (foo) { bar() }", + options: ["always", { omitLastInOneLineBlock: true }], + errors: [{ + messageId: "extraSemi", + line: 1, + column: 17, + endLine: 1, + endColumn: 18 + }] + }, + { + code: "function foo() { bar(); baz(); }", + output: "function foo() { bar(); baz() }", + options: ["always", { omitLastInOneLineBlock: true }], + errors: [{ + messageId: "extraSemi", + line: 1, + column: 30, + endLine: 1, + endColumn: 31 + }] + }, + { + code: "function foo()\n{ bar(); baz(); }", + output: "function foo()\n{ bar(); baz() }", + options: ["always", { omitLastInOneLineBlock: true }], + errors: [{ + messageId: "extraSemi", + line: 2, + column: 15, + endLine: 2, + endColumn: 16 + }] + }, + { + code: "function foo() {\n bar(); baz() }", + output: "function foo() {\n bar(); baz(); }", + options: ["always", { omitLastInOneLineBlock: true }], + errors: [{ + messageId: "missingSemi", + line: 2, + column: 14, + endLine: 2, + endColumn: 15 + }] + }, + { + code: "function foo() { bar(); baz() \n}", + output: "function foo() { bar(); baz(); \n}", + options: ["always", { omitLastInOneLineBlock: true }], + errors: [{ + messageId: "missingSemi", + line: 1, + column: 30, + endLine: 1, + endColumn: 31 + }] + }, + { + code: "class C {\nfoo() { bar(); baz(); }\n}", + output: "class C {\nfoo() { bar(); baz() }\n}", + options: ["always", { omitLastInOneLineBlock: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "extraSemi", + line: 2, + column: 21, + endLine: 2, + endColumn: 22 + }] + }, + { + code: "class C {\nfoo() \n{ bar(); baz(); }\n}", + output: "class C {\nfoo() \n{ bar(); baz() }\n}", + options: ["always", { omitLastInOneLineBlock: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "extraSemi", + line: 3, + column: 15, + endLine: 3, + endColumn: 16 + }] + }, + { + code: "class C {\nfoo() {\n bar(); baz() }\n}", + output: "class C {\nfoo() {\n bar(); baz(); }\n}", + options: ["always", { omitLastInOneLineBlock: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "missingSemi", + line: 3, + column: 14, + endLine: 3, + endColumn: 15 + }] + }, + { + code: "class C {\nfoo() { bar(); baz() \n}\n}", + output: "class C {\nfoo() { bar(); baz(); \n}\n}", + options: ["always", { omitLastInOneLineBlock: true }], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "missingSemi", + line: 2, + column: 21, + endLine: 2, + endColumn: 22 + }] + }, + { + code: "class C {\nstatic { bar(); baz(); }\n}", + output: "class C {\nstatic { bar(); baz() }\n}", + options: ["always", { omitLastInOneLineBlock: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "extraSemi", + line: 2, + column: 22, + endLine: 2, + endColumn: 23 + }] + }, + { + code: "class C {\nstatic \n{ bar(); baz(); }\n}", + output: "class C {\nstatic \n{ bar(); baz() }\n}", + options: ["always", { omitLastInOneLineBlock: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "extraSemi", + line: 3, + column: 15, + endLine: 3, + endColumn: 16 + }] + }, + { + code: "class C {\nstatic {\n bar(); baz() }\n}", + output: "class C {\nstatic {\n bar(); baz(); }\n}", + options: ["always", { omitLastInOneLineBlock: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "missingSemi", + line: 3, + column: 14, + endLine: 3, + endColumn: 15 + }] + }, + { + code: "class C {\nfoo() { bar(); baz() \n}\n}", + output: "class C {\nfoo() { bar(); baz(); \n}\n}", + options: ["always", { omitLastInOneLineBlock: true }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "missingSemi", + line: 2, + column: 21, + endLine: 2, + endColumn: 22 + }] + }, + + + // exports, "always" + { + code: "export * from 'foo'", + output: "export * from 'foo';", + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ + messageId: "missingSemi", + type: "ExportAllDeclaration", + line: 1, + column: 20, + endLine: void 0, + endColumn: void 0 + }] + }, + { + code: "export { foo } from 'foo'", + output: "export { foo } from 'foo';", + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ + messageId: "missingSemi", + type: "ExportNamedDeclaration", + line: 1, + column: 26, + endLine: void 0, + endColumn: void 0 + }] + }, + { + code: "var foo = 0;export { foo }", + output: "var foo = 0;export { foo };", + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ + messageId: "missingSemi", + type: "ExportNamedDeclaration", + line: 1, + column: 27, + endLine: void 0, + endColumn: void 0 + }] + }, + { + code: "export var foo", + output: "export var foo;", + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ + messageId: "missingSemi", + type: "VariableDeclaration", + line: 1, + column: 15, + endLine: void 0, + endColumn: void 0 + }] + }, + { + code: "export let foo", + output: "export let foo;", + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ + messageId: "missingSemi", + type: "VariableDeclaration", + line: 1, + column: 15, + endLine: void 0, + endColumn: void 0 + }] + }, + { + code: "export const FOO = 42", + output: "export const FOO = 42;", + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ + messageId: "missingSemi", + type: "VariableDeclaration", + line: 1, + column: 22, + endLine: void 0, + endColumn: void 0 + }] + }, { code: "export default foo || bar", output: "export default foo || bar;", parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ messageId: "missingSemi", - type: "ExportDefaultDeclaration" + type: "ExportDefaultDeclaration", + line: 1, + column: 26, + endLine: void 0, + endColumn: void 0 }] }, { @@ -743,7 +1506,11 @@ ruleTester.run("semi", rule, { parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ messageId: "missingSemi", - type: "ExportDefaultDeclaration" + type: "ExportDefaultDeclaration", + line: 1, + column: 34, + endLine: void 0, + endColumn: void 0 }] }, { @@ -752,7 +1519,11 @@ ruleTester.run("semi", rule, { parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ messageId: "missingSemi", - type: "ExportDefaultDeclaration" + type: "ExportDefaultDeclaration", + line: 1, + column: 24, + endLine: void 0, + endColumn: void 0 }] }, { @@ -761,7 +1532,11 @@ ruleTester.run("semi", rule, { parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ messageId: "missingSemi", - type: "ExportDefaultDeclaration" + type: "ExportDefaultDeclaration", + line: 1, + column: 25, + endLine: void 0, + endColumn: void 0 }] }, @@ -774,7 +1549,9 @@ ruleTester.run("semi", rule, { errors: [{ messageId: "extraSemi", type: "ExportAllDeclaration", + line: 1, column: 20, + endLine: 1, endColumn: 21 }] }, @@ -785,7 +1562,11 @@ ruleTester.run("semi", rule, { parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ messageId: "extraSemi", - type: "ExportNamedDeclaration" + type: "ExportNamedDeclaration", + line: 1, + column: 26, + endLine: 1, + endColumn: 27 }] }, { @@ -795,7 +1576,11 @@ ruleTester.run("semi", rule, { parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ messageId: "extraSemi", - type: "ExportNamedDeclaration" + type: "ExportNamedDeclaration", + line: 1, + column: 27, + endLine: 1, + endColumn: 28 }] }, { @@ -805,7 +1590,11 @@ ruleTester.run("semi", rule, { parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ messageId: "extraSemi", - type: "VariableDeclaration" + type: "VariableDeclaration", + line: 1, + column: 15, + endLine: 1, + endColumn: 16 }] }, { @@ -815,7 +1604,11 @@ ruleTester.run("semi", rule, { parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ messageId: "extraSemi", - type: "VariableDeclaration" + type: "VariableDeclaration", + line: 1, + column: 15, + endLine: 1, + endColumn: 16 }] }, { @@ -825,7 +1618,11 @@ ruleTester.run("semi", rule, { parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ messageId: "extraSemi", - type: "VariableDeclaration" + type: "VariableDeclaration", + line: 1, + column: 22, + endLine: 1, + endColumn: 23 }] }, { @@ -835,7 +1632,11 @@ ruleTester.run("semi", rule, { parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ messageId: "extraSemi", - type: "ExportDefaultDeclaration" + type: "ExportDefaultDeclaration", + line: 1, + column: 26, + endLine: 1, + endColumn: 27 }] }, { @@ -845,7 +1646,11 @@ ruleTester.run("semi", rule, { parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ messageId: "extraSemi", - type: "ExportDefaultDeclaration" + type: "ExportDefaultDeclaration", + line: 1, + column: 34, + endLine: 1, + endColumn: 35 }] }, { @@ -855,7 +1660,11 @@ ruleTester.run("semi", rule, { parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ messageId: "extraSemi", - type: "ExportDefaultDeclaration" + type: "ExportDefaultDeclaration", + line: 1, + column: 24, + endLine: 1, + endColumn: 25 }] }, { @@ -865,7 +1674,11 @@ ruleTester.run("semi", rule, { parserOptions: { ecmaVersion: 6, sourceType: "module" }, errors: [{ messageId: "extraSemi", - type: "ExportDefaultDeclaration" + type: "ExportDefaultDeclaration", + line: 1, + column: 25, + endLine: 1, + endColumn: 26 }] }, { @@ -874,7 +1687,9 @@ ruleTester.run("semi", rule, { options: ["never"], errors: [{ messageId: "extraSemi", + line: 1, column: 2, + endLine: 1, endColumn: 3 }] }, @@ -893,8 +1708,20 @@ ruleTester.run("semi", rule, { ].join("\n"), options: ["never"], errors: [ - "Extra semicolon.", - "Unnecessary semicolon." + { + messageId: "extraSemi", + line: 2, + column: 6, + endLine: 2, + endColumn: 7 + }, + { + message: "Unnecessary semicolon.", + line: 3, + column: 1, + endLine: 3, + endColumn: 2 + } ] }, @@ -910,7 +1737,13 @@ ruleTester.run("semi", rule, { `, options: ["never", { beforeStatementContinuationChars: "always" }], parserOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: ["Missing semicolon."] + errors: [{ + messageId: "missingSemi", + line: 2, + column: 34, + endLine: 3, + endColumn: 1 + }] }, { code: ` @@ -923,7 +1756,13 @@ ruleTester.run("semi", rule, { `, options: ["never", { beforeStatementContinuationChars: "always" }], parserOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: ["Missing semicolon."] + errors: [{ + messageId: "missingSemi", + line: 2, + column: 38, + endLine: 3, + endColumn: 1 + }] }, { code: ` @@ -940,7 +1779,13 @@ ruleTester.run("semi", rule, { `, options: ["never", { beforeStatementContinuationChars: "always" }], parserOptions: { ecmaVersion: 2015 }, - errors: ["Missing semicolon."] + errors: [{ + messageId: "missingSemi", + line: 3, + column: 27, + endLine: 4, + endColumn: 1 + }] }, { code: ` @@ -956,7 +1801,13 @@ ruleTester.run("semi", rule, { } `, options: ["never", { beforeStatementContinuationChars: "always" }], - errors: ["Missing semicolon."] + errors: [{ + messageId: "missingSemi", + line: 3, + column: 26, + endLine: 4, + endColumn: 1 + }] }, { code: ` @@ -972,7 +1823,13 @@ ruleTester.run("semi", rule, { } `, options: ["never", { beforeStatementContinuationChars: "always" }], - errors: ["Missing semicolon."] + errors: [{ + messageId: "missingSemi", + line: 3, + column: 29, + endLine: 4, + endColumn: 1 + }] }, { code: ` @@ -984,7 +1841,13 @@ ruleTester.run("semi", rule, { [1,2,3].forEach(doSomething) `, options: ["never", { beforeStatementContinuationChars: "always" }], - errors: ["Missing semicolon."] + errors: [{ + messageId: "missingSemi", + line: 2, + column: 29, + endLine: 3, + endColumn: 1 + }] }, { code: ` @@ -997,7 +1860,13 @@ ruleTester.run("semi", rule, { `, options: ["never", { beforeStatementContinuationChars: "always" }], parserOptions: { ecmaVersion: 2015 }, - errors: ["Missing semicolon."] + errors: [{ + messageId: "missingSemi", + line: 2, + column: 35, + endLine: 3, + endColumn: 1 + }] }, { code: ` @@ -1010,7 +1879,13 @@ ruleTester.run("semi", rule, { `, options: ["never", { beforeStatementContinuationChars: "never" }], parserOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: ["Extra semicolon."] + errors: [{ + messageId: "extraSemi", + line: 2, + column: 34, + endLine: 2, + endColumn: 35 + }] }, { code: ` @@ -1023,7 +1898,13 @@ ruleTester.run("semi", rule, { `, options: ["never", { beforeStatementContinuationChars: "never" }], parserOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: ["Extra semicolon."] + errors: [{ + messageId: "extraSemi", + line: 2, + column: 38, + endLine: 2, + endColumn: 39 + }] }, { code: ` @@ -1040,7 +1921,13 @@ ruleTester.run("semi", rule, { `, options: ["never", { beforeStatementContinuationChars: "never" }], parserOptions: { ecmaVersion: 2015 }, - errors: ["Extra semicolon."] + errors: [{ + messageId: "extraSemi", + line: 3, + column: 27, + endLine: 3, + endColumn: 28 + }] }, { code: ` @@ -1056,7 +1943,13 @@ ruleTester.run("semi", rule, { } `, options: ["never", { beforeStatementContinuationChars: "never" }], - errors: ["Extra semicolon."] + errors: [{ + messageId: "extraSemi", + line: 3, + column: 26, + endLine: 3, + endColumn: 27 + }] }, { code: ` @@ -1072,7 +1965,13 @@ ruleTester.run("semi", rule, { } `, options: ["never", { beforeStatementContinuationChars: "never" }], - errors: ["Extra semicolon."] + errors: [{ + messageId: "extraSemi", + line: 3, + column: 29, + endLine: 3, + endColumn: 30 + }] }, { code: ` @@ -1084,7 +1983,13 @@ ruleTester.run("semi", rule, { [1,2,3].forEach(doSomething) `, options: ["never", { beforeStatementContinuationChars: "never" }], - errors: ["Extra semicolon."] + errors: [{ + messageId: "extraSemi", + line: 2, + column: 29, + endLine: 2, + endColumn: 30 + }] }, { code: ` @@ -1097,7 +2002,13 @@ ruleTester.run("semi", rule, { `, options: ["never", { beforeStatementContinuationChars: "never" }], parserOptions: { ecmaVersion: 2015 }, - errors: ["Extra semicolon."] + errors: [{ + messageId: "extraSemi", + line: 2, + column: 35, + endLine: 2, + endColumn: 36 + }] }, { code: ` @@ -1110,7 +2021,13 @@ ruleTester.run("semi", rule, { `, options: ["never", { beforeStatementContinuationChars: "never" }], parserOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: ["Extra semicolon."] + errors: [{ + messageId: "extraSemi", + line: 3, + column: 17, + endLine: 3, + endColumn: 18 + }] }, { code: ` @@ -1123,7 +2040,13 @@ ruleTester.run("semi", rule, { `, options: ["never", { beforeStatementContinuationChars: "never" }], parserOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: ["Extra semicolon."] + errors: [{ + messageId: "extraSemi", + line: 3, + column: 17, + endLine: 3, + endColumn: 18 + }] }, { code: ` @@ -1139,7 +2062,13 @@ ruleTester.run("semi", rule, { } `, options: ["never", { beforeStatementContinuationChars: "never" }], - errors: ["Extra semicolon."] + errors: [{ + messageId: "extraSemi", + line: 4, + column: 21, + endLine: 4, + endColumn: 22 + }] }, { code: ` @@ -1155,7 +2084,13 @@ ruleTester.run("semi", rule, { } `, options: ["never", { beforeStatementContinuationChars: "never" }], - errors: ["Extra semicolon."] + errors: [{ + messageId: "extraSemi", + line: 4, + column: 21, + endLine: 4, + endColumn: 22 + }] }, { code: ` @@ -1171,7 +2106,13 @@ ruleTester.run("semi", rule, { } `, options: ["never", { beforeStatementContinuationChars: "never" }], - errors: ["Extra semicolon."] + errors: [{ + messageId: "extraSemi", + line: 4, + column: 21, + endLine: 4, + endColumn: 22 + }] }, { code: ` @@ -1183,7 +2124,13 @@ ruleTester.run("semi", rule, { [1,2,3].forEach(doSomething) `, options: ["never", { beforeStatementContinuationChars: "never" }], - errors: ["Extra semicolon."] + errors: [{ + messageId: "extraSemi", + line: 3, + column: 17, + endLine: 3, + endColumn: 18 + }] }, { code: ` @@ -1196,7 +2143,172 @@ ruleTester.run("semi", rule, { `, options: ["never", { beforeStatementContinuationChars: "never" }], parserOptions: { ecmaVersion: 2015 }, - errors: ["Extra semicolon."] + errors: [{ + messageId: "extraSemi", + line: 3, + column: 17, + endLine: 3, + endColumn: 18 + }] + }, + + // Class fields + { + code: "class C { foo }", + output: "class C { foo; }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "missingSemi", + line: 1, + column: 14, + endLine: 1, + endColumn: 15 + }] + }, + { + code: "class C { foo }", + output: "class C { foo; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "missingSemi", + line: 1, + column: 14, + endLine: 1, + endColumn: 15 + }] + }, + { + code: "class C { foo; }", + output: "class C { foo }", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "extraSemi", + line: 1, + column: 14, + endLine: 1, + endColumn: 15 + }] + }, + { + code: "class C { foo\n[bar]; }", + output: "class C { foo;\n[bar]; }", + options: ["always"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "missingSemi", + line: 1, + column: 14, + endLine: 2, + endColumn: 1 + }] + }, + + // class fields + { + code: "class C { [get];\nfoo\n}", + output: "class C { [get]\nfoo\n}", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "extraSemi", + line: 1, + column: 16, + endLine: 1, + endColumn: 17 + }] + }, + { + code: "class C { [set];\nfoo\n}", + output: "class C { [set]\nfoo\n}", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "extraSemi", + line: 1, + column: 16, + endLine: 1, + endColumn: 17 + }] + }, + { + code: "class C { #get;\nfoo\n}", + output: "class C { #get\nfoo\n}", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "extraSemi", + line: 1, + column: 15, + endLine: 1, + endColumn: 16 + }] + }, + { + code: "class C { #set;\nfoo\n}", + output: "class C { #set\nfoo\n}", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "extraSemi", + line: 1, + column: 15, + endLine: 1, + endColumn: 16 + }] + }, + { + code: "class C { #static;\nfoo\n}", + output: "class C { #static\nfoo\n}", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "extraSemi", + line: 1, + column: 18, + endLine: 1, + endColumn: 19 + }] + }, + { + code: "class C { get=1;\nfoo\n}", + output: "class C { get=1\nfoo\n}", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "extraSemi", + line: 1, + column: 16, + endLine: 1, + endColumn: 17 + }] + }, + { + code: "class C { static static;\nfoo\n}", + output: "class C { static static\nfoo\n}", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "extraSemi", + line: 1, + column: 24, + endLine: 1, + endColumn: 25 + }] + }, + { + code: "class C { static;\n}", + output: "class C { static\n}", + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "extraSemi", + line: 1, + column: 17, + endLine: 1, + endColumn: 18 + }] } ] }); diff --git a/eslint/tests/lib/rules/space-before-blocks.js b/eslint/tests/lib/rules/space-before-blocks.js index 7424aec..49af961 100644 --- a/eslint/tests/lib/rules/space-before-blocks.js +++ b/eslint/tests/lib/rules/space-before-blocks.js @@ -18,6 +18,7 @@ const rule = require("../../../lib/rules/space-before-blocks"), //------------------------------------------------------------------------------ const ruleTester = new RuleTester(), + alwaysArgs = ["always"], neverArgs = ["never"], functionsOnlyArgs = [{ functions: "always", keywords: "never", classes: "never" }], keywordOnlyArgs = [{ functions: "never", keywords: "always", classes: "never" }], @@ -193,7 +194,27 @@ ruleTester.run("space-before-blocks", rule, { "if(a) {}else{}", { code: "if(a){}else {}", options: neverArgs }, { code: "try {}catch(a){}", options: functionsOnlyArgs }, - { code: "export default class{}", options: classesOnlyArgs, parserOptions: { ecmaVersion: 6, sourceType: "module" } } + { code: "export default class{}", options: classesOnlyArgs, parserOptions: { ecmaVersion: 6, sourceType: "module" } }, + + // https://github.com/eslint/eslint/issues/15082 + { code: "switch(x) { case 9:{ break; } }", options: alwaysArgs }, + { code: "switch(x){ case 9: { break; } }", options: neverArgs }, + { code: "switch(x) { case (9):{ break; } }", options: alwaysArgs }, + { code: "switch(x){ case (9): { break; } }", options: neverArgs }, + { code: "switch(x) { default:{ break; } }", options: alwaysArgs }, + { code: "switch(x){ default: { break; } }", options: neverArgs }, + + // not conflict with `keyword-spacing` + { + code: "(class{ static{} })", + options: ["always"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "(class { static {} })", + options: ["never"], + parserOptions: { ecmaVersion: 2022 } + } ], invalid: [ { @@ -571,6 +592,68 @@ ruleTester.run("space-before-blocks", rule, { options: neverArgs, parser: fixtureParser("space-before-blocks", "return-type-keyword-2"), errors: [expectedNoSpacingError] + }, + + // https://github.com/eslint/eslint/issues/15082 regression tests (only blocks after switch case colons should be excluded) + { + code: "label:{}", + output: "label: {}", + options: alwaysArgs, + errors: [expectedSpacingError] + }, + { + code: "label: {}", + output: "label:{}", + options: neverArgs, + errors: [expectedNoSpacingError] + }, + { + code: "switch(x) { case 9: label:{ break; } }", + output: "switch(x) { case 9: label: { break; } }", + options: alwaysArgs, + errors: [expectedSpacingError] + }, + { + code: "switch(x){ case 9: label: { break; } }", + output: "switch(x){ case 9: label:{ break; } }", + options: neverArgs, + errors: [expectedNoSpacingError] + }, + { + code: "switch(x) { case 9: if(y){ break; } }", + output: "switch(x) { case 9: if(y) { break; } }", + options: alwaysArgs, + errors: [expectedSpacingError] + }, + { + code: "switch(x){ case 9: if(y) { break; } }", + output: "switch(x){ case 9: if(y){ break; } }", + options: neverArgs, + errors: [expectedNoSpacingError] + }, + { + code: "switch(x) { case 9: y;{ break; } }", + output: "switch(x) { case 9: y; { break; } }", + options: alwaysArgs, + errors: [expectedSpacingError] + }, + { + code: "switch(x){ case 9: y; { break; } }", + output: "switch(x){ case 9: y;{ break; } }", + options: neverArgs, + errors: [expectedNoSpacingError] + }, + { + code: "switch(x) { case 9: switch(y){} }", + output: "switch(x) { case 9: switch(y) {} }", + options: alwaysArgs, + errors: [expectedSpacingError] + }, + { + code: "switch(x){ case 9: switch(y) {} }", + output: "switch(x){ case 9: switch(y){} }", + options: neverArgs, + errors: [expectedNoSpacingError] } ] }); diff --git a/eslint/tests/lib/rules/space-infix-ops.js b/eslint/tests/lib/rules/space-infix-ops.js index 2e6f423..c2a5c55 100644 --- a/eslint/tests/lib/rules/space-infix-ops.js +++ b/eslint/tests/lib/rules/space-infix-ops.js @@ -13,6 +13,10 @@ const rule = require("../../../lib/rules/space-infix-ops"), { RuleTester } = require("../../../lib/rule-tester"), parser = require("../../fixtures/fixture-parser"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester(); ruleTester.run("space-infix-ops", rule, { @@ -49,9 +53,17 @@ ruleTester.run("space-infix-ops", rule, { // TypeScript Type Aliases { code: "type Foo = T;", parser: parser("typescript-parsers/type-alias"), parserOptions: { ecmaVersion: 6 } }, + // Logical Assignments { code: "a &&= b", parserOptions: { ecmaVersion: 2021 } }, { code: "a ||= b", parserOptions: { ecmaVersion: 2021 } }, - { code: "a ??= b", parserOptions: { ecmaVersion: 2021 } } + { code: "a ??= b", parserOptions: { ecmaVersion: 2021 } }, + + // Class Fields + { code: "class C { a; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { a = b; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { 'a' = b; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { [a] = b; }", parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { #a = b; }", parserOptions: { ecmaVersion: 2022 } } ], invalid: [ { @@ -492,6 +504,7 @@ ruleTester.run("space-infix-ops", rule, { }] }, + // Logical Assignments { code: "a&&=b", output: "a &&= b", @@ -533,6 +546,36 @@ ruleTester.run("space-infix-ops", rule, { endColumn: 5, type: "AssignmentExpression" }] + }, + + // Class Fields + { + code: "class C { a=b; }", + output: "class C { a = b; }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "missingSpace", + data: { operator: "=" }, + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + type: "PropertyDefinition" + }] + }, + { + code: "class C { [a ]= b; }", + output: "class C { [a ] = b; }", + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "missingSpace", + data: { operator: "=" }, + line: 1, + column: 15, + endLine: 1, + endColumn: 16, + type: "PropertyDefinition" + }] } ] }); diff --git a/eslint/tests/lib/rules/space-unary-ops.js b/eslint/tests/lib/rules/space-unary-ops.js index c56e013..ccee19c 100644 --- a/eslint/tests/lib/rules/space-unary-ops.js +++ b/eslint/tests/lib/rules/space-unary-ops.js @@ -250,6 +250,11 @@ ruleTester.run("space-unary-ops", rule, { code: "function *foo () { yield(0) }", options: [{ words: false, overrides: { yield: false } }], parserOptions: { ecmaVersion: 6 } + }, + { + code: "class C { #x; *foo(bar) { yield#x in bar; } }", + options: [{ words: false }], + parserOptions: { ecmaVersion: 2022 } } ], @@ -805,6 +810,19 @@ ruleTester.run("space-unary-ops", rule, { line: 1, column: 24 }] + }, + { + code: "class C { #x; *foo(bar) { yield #x in bar; } }", + output: "class C { #x; *foo(bar) { yield#x in bar; } }", + options: [{ words: false }], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "unexpectedAfterWord", + data: { word: "yield" }, + type: "YieldExpression", + line: 1, + column: 27 + }] } ] }); diff --git a/eslint/tests/lib/rules/spaced-comment.js b/eslint/tests/lib/rules/spaced-comment.js index 5d3d68f..7ca6384 100644 --- a/eslint/tests/lib/rules/spaced-comment.js +++ b/eslint/tests/lib/rules/spaced-comment.js @@ -4,9 +4,17 @@ */ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + const rule = require("../../../lib/rules/spaced-comment"), { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester(), validShebangProgram = "#!/path/to/node\nvar a = 3;"; diff --git a/eslint/tests/lib/rules/strict.js b/eslint/tests/lib/rules/strict.js index 92abffd..e3ea705 100644 --- a/eslint/tests/lib/rules/strict.js +++ b/eslint/tests/lib/rules/strict.js @@ -12,6 +12,10 @@ const rule = require("../../../lib/rules/strict"), { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester(); ruleTester.run("strict", rule, { @@ -84,7 +88,20 @@ ruleTester.run("strict", rule, { "function foo() { 'use strict'; return; }", { code: "'use strict'; function foo() { return; }", parserOptions: { ecmaFeatures: { globalReturn: true } } }, { code: "function foo() { return; }", parserOptions: { ecmaVersion: 6, sourceType: "module" } }, - { code: "function foo() { return; }", parserOptions: { ecmaFeatures: { impliedStrict: true } } } + { code: "function foo() { return; }", parserOptions: { ecmaFeatures: { impliedStrict: true } } }, + + // class static blocks do not have directive prologues, therefore this rule should never require od disallow "use strict" statement in them. + { code: "'use strict'; class C { static { foo; } }", options: ["global"], parserOptions: { ecmaVersion: 2022 } }, + { code: "'use strict'; class C { static { 'use strict'; } }", options: ["global"], parserOptions: { ecmaVersion: 2022 } }, + { code: "'use strict'; class C { static { 'use strict'; 'use strict'; } }", options: ["global"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo; } }", options: ["function"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { 'use strict'; } }", options: ["function"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { 'use strict'; 'use strict'; } }", options: ["function"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { foo; } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { 'use strict'; } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { 'use strict'; 'use strict'; } }", options: ["never"], parserOptions: { ecmaVersion: 2022 } }, + { code: "class C { static { 'use strict'; } }", options: ["safe"], parserOptions: { ecmaVersion: 2022, sourceType: "module" } }, + { code: "class C { static { 'use strict'; } }", options: ["safe"], parserOptions: { ecmaVersion: 2022, ecmaFeatures: { impliedStrict: true } } } ], invalid: [ @@ -404,7 +421,20 @@ ruleTester.run("strict", rule, { parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unnecessaryInClasses", type: "ExpressionStatement" }] }, - + { + code: "class A { field = () => { \"use strict\"; } }", + output: "class A { field = () => { } }", + options: ["function"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unnecessaryInClasses", type: "ExpressionStatement" }] + }, + { + code: "class A { field = function() { \"use strict\"; } }", + output: "class A { field = function() { } }", + options: ["function"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unnecessaryInClasses", type: "ExpressionStatement" }] + }, // "safe" mode corresponds to "global" if ecmaFeatures.globalReturn is true, otherwise "function" { @@ -572,7 +602,60 @@ ruleTester.run("strict", rule, { options: ["function"], parserOptions: { ecmaVersion: 6 }, errors: ["Use the function form of 'use strict'."] - } + }, + // functions inside class static blocks should be checked + { + code: "'use strict'; class C { static { function foo() { \n'use strict'; } } }", + output: null, + options: ["global"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "global", line: 2 }] + }, + { + code: "class C { static { function foo() { \n'use strict'; } } }", + output: null, + options: ["never"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "never", line: 2 }] + }, + { + code: "class C { static { function foo() { \n'use strict'; } } }", + output: "class C { static { function foo() { \n } } }", + options: ["safe"], + parserOptions: { ecmaVersion: 2022, sourceType: "module" }, + errors: [{ messageId: "module", line: 2 }] + }, + { + code: "class C { static { function foo() { \n'use strict'; } } }", + output: "class C { static { function foo() { \n } } }", + options: ["safe"], + parserOptions: { ecmaVersion: 2022, ecmaFeatures: { impliedStrict: true } }, + errors: [{ messageId: "implied", line: 2 }] + }, + { + code: "function foo() {'use strict'; class C { static { function foo() { \n'use strict'; } } } }", + output: "function foo() {'use strict'; class C { static { function foo() { \n } } } }", + options: ["function"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unnecessary", line: 2 }] + }, + { + code: "class C { static { function foo() { \n'use strict'; } } }", + output: "class C { static { function foo() { \n } } }", + options: ["function"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "unnecessaryInClasses", line: 2 }] + }, + { + code: "class C { static { function foo() { \n'use strict';\n'use strict'; } } }", + output: "class C { static { function foo() { \n\n } } }", + options: ["function"], + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "unnecessaryInClasses", line: 2 }, + { messageId: "multiple", line: 3 } + ] + } ] }); diff --git a/eslint/tests/lib/rules/symbol-description.js b/eslint/tests/lib/rules/symbol-description.js index 36391fc..55cbbbc 100644 --- a/eslint/tests/lib/rules/symbol-description.js +++ b/eslint/tests/lib/rules/symbol-description.js @@ -12,6 +12,10 @@ const rule = require("../../../lib/rules/symbol-description"); const { RuleTester } = require("../../../lib/rule-tester"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester({ env: { es6: true } }); ruleTester.run("symbol-description", rule, { diff --git a/eslint/tests/lib/rules/unicode-bom.js b/eslint/tests/lib/rules/unicode-bom.js index 0b94a69..62abee6 100644 --- a/eslint/tests/lib/rules/unicode-bom.js +++ b/eslint/tests/lib/rules/unicode-bom.js @@ -41,24 +41,52 @@ ruleTester.run("unicode-bom", rule, { code: "var a = 123;", output: "\uFEFFvar a = 123;", options: ["always"], - errors: [expectedError] + errors: [{ + ...expectedError, + line: 1, + column: 1, + endLine: void 0, + endColumn: void 0 + + }] }, { code: " // here's a comment \nvar a = 123;", output: "\uFEFF // here's a comment \nvar a = 123;", options: ["always"], - errors: [expectedError] + errors: [{ + ...expectedError, + line: 1, + column: 1, + endLine: void 0, + endColumn: void 0 + + }] }, { code: "\uFEFF var a = 123;", output: " var a = 123;", - errors: [unexpectedError] + errors: [{ + ...unexpectedError, + line: 1, + column: 1, + endLine: void 0, + endColumn: void 0 + + }] }, { code: "\uFEFF var a = 123;", output: " var a = 123;", options: ["never"], - errors: [unexpectedError] + errors: [{ + ...unexpectedError, + line: 1, + column: 1, + endLine: void 0, + endColumn: void 0 + + }] } ] }); diff --git a/eslint/tests/lib/rules/use-isnan.js b/eslint/tests/lib/rules/use-isnan.js index c5a4b6b..a9bfe3a 100644 --- a/eslint/tests/lib/rules/use-isnan.js +++ b/eslint/tests/lib/rules/use-isnan.js @@ -36,6 +36,19 @@ ruleTester.run("use-isnan", rule, { "foo(NaN / 2)", "foo(2 / NaN)", "var x; if (x = NaN) { }", + "var x = Number.NaN;", + "isNaN(Number.NaN) === true;", + "Number.isNaN(Number.NaN) === true;", + "foo(Number.NaN + 1);", + "foo(1 + Number.NaN);", + "foo(Number.NaN - 1)", + "foo(1 - Number.NaN)", + "foo(Number.NaN * 2)", + "foo(2 * Number.NaN)", + "foo(Number.NaN / 2)", + "foo(2 / Number.NaN)", + "var x; if (x = Number.NaN) { }", + "x === Number[NaN];", //------------------------------------------------------------------------------ // enforceForSwitchCase @@ -105,6 +118,62 @@ ruleTester.run("use-isnan", rule, { code: "switch(foo) { case bar: break; case 1: break; default: break; }", options: [{ enforceForSwitchCase: true }] }, + { + code: "switch(Number.NaN) { case foo: break; }", + options: [{ enforceForSwitchCase: false }] + }, + { + code: "switch(foo) { case Number.NaN: break; }", + options: [{ enforceForSwitchCase: false }] + }, + { + code: "switch(NaN) { case Number.NaN: break; }", + options: [{ enforceForSwitchCase: false }] + }, + { + code: "switch(foo) { case bar: break; case Number.NaN: break; default: break; }", + options: [{ enforceForSwitchCase: false }] + }, + { + code: "switch(foo) { case bar: Number.NaN; }", + options: [{ enforceForSwitchCase: true }] + }, + { + code: "switch(foo) { default: Number.NaN; }", + options: [{ enforceForSwitchCase: true }] + }, + { + code: "switch(Number.Nan) {}", + options: [{ enforceForSwitchCase: true }] + }, + { + code: "switch('Number.NaN') { default: break; }", + options: [{ enforceForSwitchCase: true }] + }, + { + code: "switch(foo(Number.NaN)) {}", + options: [{ enforceForSwitchCase: true }] + }, + { + code: "switch(foo.Number.NaN) {}", + options: [{ enforceForSwitchCase: true }] + }, + { + code: "switch(foo) { case Number.Nan: break }", + options: [{ enforceForSwitchCase: true }] + }, + { + code: "switch(foo) { case 'Number.NaN': break }", + options: [{ enforceForSwitchCase: true }] + }, + { + code: "switch(foo) { case foo(Number.NaN): break }", + options: [{ enforceForSwitchCase: true }] + }, + { + code: "switch(foo) { case foo.Number.NaN: break }", + options: [{ enforceForSwitchCase: true }] + }, //------------------------------------------------------------------------------ // enforceForIndexOf @@ -112,6 +181,8 @@ ruleTester.run("use-isnan", rule, { "foo.indexOf(NaN)", "foo.lastIndexOf(NaN)", + "foo.indexOf(Number.NaN)", + "foo.lastIndexOf(Number.NaN)", { code: "foo.indexOf(NaN)", options: [{}] @@ -200,6 +271,79 @@ ruleTester.run("use-isnan", rule, { { code: "foo.lastIndexOf(NaN())", options: [{ enforceForIndexOf: true }] + }, + { + code: "foo.indexOf(Number.NaN)", + options: [{}] + }, + { + code: "foo.lastIndexOf(Number.NaN)", + options: [{}] + }, + { + code: "foo.indexOf(Number.NaN)", + options: [{ enforceForIndexOf: false }] + }, + { + code: "foo.lastIndexOf(Number.NaN)", + options: [{ enforceForIndexOf: false }] + }, + { + code: "indexOf(Number.NaN)", + options: [{ enforceForIndexOf: true }] + }, + { + code: "lastIndexOf(Number.NaN)", + options: [{ enforceForIndexOf: true }] + }, + { + code: "new foo.indexOf(Number.NaN)", + options: [{ enforceForIndexOf: true }] + }, + { + code: "foo.bar(Number.NaN)", + options: [{ enforceForIndexOf: true }] + }, + { + code: "foo.IndexOf(Number.NaN)", + options: [{ enforceForIndexOf: true }] + }, + { + code: "foo[indexOf](Number.NaN)", + options: [{ enforceForIndexOf: true }] + }, + { + code: "foo[lastIndexOf](Number.NaN)", + options: [{ enforceForIndexOf: true }] + }, + { + code: "indexOf.foo(Number.NaN)", + options: [{ enforceForIndexOf: true }] + }, + { + code: "foo.lastIndexOf(Number.Nan)", + options: [{ enforceForIndexOf: true }] + }, + { + code: "foo.indexOf(a, Number.NaN)", + options: [{ enforceForIndexOf: true }] + }, + { + code: "foo.lastIndexOf(Number.NaN, b)", + options: [{ enforceForIndexOf: true }] + }, + { + code: "foo.lastIndexOf(Number.NaN, NaN)", + options: [{ enforceForIndexOf: true }] + }, + { + code: "foo.indexOf(...Number.NaN)", + options: [{ enforceForIndexOf: true }], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "foo.lastIndexOf(Number.NaN())", + options: [{ enforceForIndexOf: true }] } ], invalid: [ @@ -267,6 +411,79 @@ ruleTester.run("use-isnan", rule, { code: "\"abc\" >= NaN;", errors: [comparisonError] }, + { + code: "123 == Number.NaN;", + errors: [comparisonError] + }, + { + code: "123 === Number.NaN;", + errors: [comparisonError] + }, + { + code: "Number.NaN === \"abc\";", + errors: [comparisonError] + }, + { + code: "Number.NaN == \"abc\";", + errors: [comparisonError] + }, + { + code: "123 != Number.NaN;", + errors: [comparisonError] + }, + { + code: "123 !== Number.NaN;", + errors: [comparisonError] + }, + { + code: "Number.NaN !== \"abc\";", + errors: [comparisonError] + }, + { + code: "Number.NaN != \"abc\";", + errors: [comparisonError] + }, + { + code: "Number.NaN < \"abc\";", + errors: [comparisonError] + }, + { + code: "\"abc\" < Number.NaN;", + errors: [comparisonError] + }, + { + code: "Number.NaN > \"abc\";", + errors: [comparisonError] + }, + { + code: "\"abc\" > Number.NaN;", + errors: [comparisonError] + }, + { + code: "Number.NaN <= \"abc\";", + errors: [comparisonError] + }, + { + code: "\"abc\" <= Number.NaN;", + errors: [comparisonError] + }, + { + code: "Number.NaN >= \"abc\";", + errors: [comparisonError] + }, + { + code: "\"abc\" >= Number.NaN;", + errors: [comparisonError] + }, + { + code: "x === Number?.NaN;", + parserOptions: { ecmaVersion: 2020 }, + errors: [comparisonError] + }, + { + code: "x === Number['NaN'];", + errors: [comparisonError] + }, //------------------------------------------------------------------------------ // enforceForSwitchCase @@ -351,6 +568,85 @@ ruleTester.run("use-isnan", rule, { { messageId: "caseNaN", type: "SwitchCase", column: 15 } ] }, + { + code: "switch(Number.NaN) { case foo: break; }", + errors: [{ messageId: "switchNaN", type: "SwitchStatement", column: 1 }] + }, + { + code: "switch(foo) { case Number.NaN: break; }", + errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 15 }] + }, + { + code: "switch(Number.NaN) { case foo: break; }", + options: [{}], + errors: [{ messageId: "switchNaN", type: "SwitchStatement", column: 1 }] + }, + { + code: "switch(foo) { case Number.NaN: break; }", + options: [{}], + errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 15 }] + }, + { + code: "switch(Number.NaN) {}", + options: [{ enforceForSwitchCase: true }], + errors: [{ messageId: "switchNaN", type: "SwitchStatement", column: 1 }] + }, + { + code: "switch(Number.NaN) { case foo: break; }", + options: [{ enforceForSwitchCase: true }], + errors: [{ messageId: "switchNaN", type: "SwitchStatement", column: 1 }] + }, + { + code: "switch(Number.NaN) { default: break; }", + options: [{ enforceForSwitchCase: true }], + errors: [{ messageId: "switchNaN", type: "SwitchStatement", column: 1 }] + }, + { + code: "switch(Number.NaN) { case foo: break; default: break; }", + options: [{ enforceForSwitchCase: true }], + errors: [{ messageId: "switchNaN", type: "SwitchStatement", column: 1 }] + }, + { + code: "switch(foo) { case Number.NaN: }", + options: [{ enforceForSwitchCase: true }], + errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 15 }] + }, + { + code: "switch(foo) { case Number.NaN: break; }", + options: [{ enforceForSwitchCase: true }], + errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 15 }] + }, + { + code: "switch(foo) { case (Number.NaN): break; }", + options: [{ enforceForSwitchCase: true }], + errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 15 }] + }, + { + code: "switch(foo) { case bar: break; case Number.NaN: break; default: break; }", + options: [{ enforceForSwitchCase: true }], + errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 32 }] + }, + { + code: "switch(foo) { case bar: case Number.NaN: default: break; }", + options: [{ enforceForSwitchCase: true }], + errors: [{ messageId: "caseNaN", type: "SwitchCase", column: 25 }] + }, + { + code: "switch(foo) { case bar: break; case NaN: break; case baz: break; case Number.NaN: break; }", + options: [{ enforceForSwitchCase: true }], + errors: [ + { messageId: "caseNaN", type: "SwitchCase", column: 32 }, + { messageId: "caseNaN", type: "SwitchCase", column: 66 } + ] + }, + { + code: "switch(Number.NaN) { case Number.NaN: break; }", + options: [{ enforceForSwitchCase: true }], + errors: [ + { messageId: "switchNaN", type: "SwitchStatement", column: 1 }, + { messageId: "caseNaN", type: "SwitchCase", column: 22 } + ] + }, //------------------------------------------------------------------------------ // enforceForIndexOf @@ -403,6 +699,54 @@ ruleTester.run("use-isnan", rule, { options: [{ enforceForIndexOf: true }], parserOptions: { ecmaVersion: 2020 }, errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }] + }, + { + code: "foo.indexOf(Number.NaN)", + options: [{ enforceForIndexOf: true }], + errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "indexOf" } }] + }, + { + code: "foo.lastIndexOf(Number.NaN)", + options: [{ enforceForIndexOf: true }], + errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "lastIndexOf" } }] + }, + { + code: "foo['indexOf'](Number.NaN)", + options: [{ enforceForIndexOf: true }], + errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "indexOf" } }] + }, + { + code: "foo['lastIndexOf'](Number.NaN)", + options: [{ enforceForIndexOf: true }], + errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "lastIndexOf" } }] + }, + { + code: "foo().indexOf(Number.NaN)", + options: [{ enforceForIndexOf: true }], + errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "indexOf" } }] + }, + { + code: "foo.bar.lastIndexOf(Number.NaN)", + options: [{ enforceForIndexOf: true }], + errors: [{ messageId: "indexOfNaN", type: "CallExpression", data: { methodName: "lastIndexOf" } }] + }, + { + code: "foo.indexOf?.(Number.NaN)", + options: [{ enforceForIndexOf: true }], + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }] + }, + { + code: "foo?.indexOf(Number.NaN)", + options: [{ enforceForIndexOf: true }], + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }] + }, + { + code: "(foo?.indexOf)(Number.NaN)", + options: [{ enforceForIndexOf: true }], + parserOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "indexOfNaN", data: { methodName: "indexOf" } }] } ] }); diff --git a/eslint/tests/lib/rules/utils/ast-utils.js b/eslint/tests/lib/rules/utils/ast-utils.js index d0ad274..7789de7 100644 --- a/eslint/tests/lib/rules/utils/ast-utils.js +++ b/eslint/tests/lib/rules/utils/ast-utils.js @@ -185,7 +185,6 @@ describe("ast-utils", () => { * Asserts the node is NOT a directive comment * @param {ASTNode} node node to assert * @returns {void} - * */ function assertFalse(node) { assert.isFalse(astUtils.isDirectiveComment(node)); @@ -195,7 +194,6 @@ describe("ast-utils", () => { * Asserts the node is a directive comment * @param {ASTNode} node node to assert * @returns {void} - * */ function assertTrue(node) { assert.isTrue(astUtils.isDirectiveComment(node)); @@ -406,7 +404,7 @@ describe("ast-utils", () => { describe("getStaticStringValue", () => { - /* eslint-disable quote-props */ + /* eslint-disable quote-props -- Make consistent here for readability */ const expectedResults = { // string literals @@ -470,7 +468,7 @@ describe("ast-utils", () => { "this": null, "(function () {})": null }; - /* eslint-enable quote-props */ + /* eslint-enable quote-props -- Make consistent here for readability */ Object.keys(expectedResults).forEach(key => { it(`should return ${expectedResults[key]} for ${key}`, () => { @@ -878,7 +876,17 @@ describe("ast-utils", () => { "class A { static *foo() {} }": "static generator method 'foo'", "class A { static async foo() {} }": "static async method 'foo'", "class A { static get foo() {} }": "static getter 'foo'", - "class A { static set foo(a) {} }": "static setter 'foo'" + "class A { static set foo(a) {} }": "static setter 'foo'", + "class A { foo = () => {}; }": "method 'foo'", + "class A { foo = function() {}; }": "method 'foo'", + "class A { foo = function bar() {}; }": "method 'foo'", + "class A { static foo = () => {}; }": "static method 'foo'", + "class A { '#foo' = () => {}; }": "method '#foo'", + "class A { #foo = () => {}; }": "private method #foo", + "class A { static #foo = () => {}; }": "static private method #foo", + "class A { '#foo'() {} }": "method '#foo'", + "class A { #foo() {} }": "private method #foo", + "class A { static #foo() {} }": "static private method #foo" }; Object.keys(expectedResults).forEach(key => { @@ -892,7 +900,7 @@ describe("ast-utils", () => { }) }))); - linter.verify(key, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 8 } }); + linter.verify(key, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 13 } }); }); }); }); @@ -940,7 +948,12 @@ describe("ast-utils", () => { "class A { static *foo() {} }": [10, 21], "class A { static async foo() {} }": [10, 26], "class A { static get foo() {} }": [10, 24], - "class A { static set foo(a) {} }": [10, 24] + "class A { static set foo(a) {} }": [10, 24], + "class A { foo = function() {}; }": [10, 24], + "class A { foo = function bar() {}; }": [10, 28], + "class A { static foo = function() {}; }": [10, 31], + "class A { foo = () => {}; }": [10, 16], + "class A { foo = arg => {}; }": [10, 16] }; Object.keys(expectedResults).forEach(key => { @@ -965,7 +978,7 @@ describe("ast-utils", () => { }) }))); - linter.verify(key, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 8 } }, "test.js", true); + linter.verify(key, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 13 } }, "test.js", true); }); }); }); @@ -1006,7 +1019,7 @@ describe("ast-utils", () => { describe("getNextLocation", () => { - /* eslint-disable quote-props */ + /* eslint-disable quote-props -- Make consistent here for readability */ const expectedResults = { "": [[1, 0], null], "\n": [[1, 0], [2, 0], null], @@ -1028,7 +1041,7 @@ describe("ast-utils", () => { "a\t": [[1, 0], [1, 1], [1, 2], null], "a \n": [[1, 0], [1, 1], [1, 2], [2, 0], null] }; - /* eslint-enable quote-props */ + /* eslint-enable quote-props -- Make consistent here for readability */ Object.keys(expectedResults).forEach(code => { it(`should return expected locations for "${code}".`, () => { @@ -1482,7 +1495,10 @@ describe("ast-utils", () => { [["(", "123invalidtoken"], false], [["(", "1n"], true], [["1n", "+"], true], - [["1n", "in"], false] + [["1n", "in"], false], + [["return", "#x"], true], + [["yield", "#x"], true], + [["get", "#x"], true] ]); CASES.forEach((expectedResult, tokenStrings) => { @@ -1557,7 +1573,7 @@ describe("ast-utils", () => { }, nodeB: { type: "Literal", - value: /(?:)/, // eslint-disable-line require-unicode-regexp + value: /(?:)/, // eslint-disable-line require-unicode-regexp -- Checking non-Unicode regex regex: { pattern: "(?:)", flags: "" } }, expected: false @@ -1650,7 +1666,7 @@ describe("ast-utils", () => { describe("hasOctalOrNonOctalDecimalEscapeSequence", () => { - /* eslint-disable quote-props */ + /* eslint-disable quote-props -- Make consistent here for readability */ const expectedResults = { "\\1": true, "\\2": true, @@ -1722,7 +1738,7 @@ describe("ast-utils", () => { "foo\\\nbar": false, "128\\\n349": false }; - /* eslint-enable quote-props */ + /* eslint-enable quote-props -- Make consistent here for readability */ Object.keys(expectedResults).forEach(key => { it(`should return ${expectedResults[key]} for ${key}`, () => { diff --git a/eslint/tests/lib/rules/vars-on-top.js b/eslint/tests/lib/rules/vars-on-top.js index 92e1a0d..e2b1bb6 100644 --- a/eslint/tests/lib/rules/vars-on-top.js +++ b/eslint/tests/lib/rules/vars-on-top.js @@ -191,6 +191,84 @@ ruleTester.run("vars-on-top", rule, { ecmaVersion: 6, sourceType: "module" } + }, + { + code: [ + "class C {", + " static {", + " var x;", + " }", + "}" + ].join("\n"), + parserOptions: { + ecmaVersion: 2022 + } + }, + { + code: [ + "class C {", + " static {", + " var x;", + " foo();", + " }", + "}" + ].join("\n"), + parserOptions: { + ecmaVersion: 2022 + } + }, + { + code: [ + "class C {", + " static {", + " var x;", + " var y;", + " }", + "}" + ].join("\n"), + parserOptions: { + ecmaVersion: 2022 + } + }, + { + code: [ + "class C {", + " static {", + " var x;", + " var y;", + " foo();", + " }", + "}" + ].join("\n"), + parserOptions: { + ecmaVersion: 2022 + } + }, + { + code: [ + "class C {", + " static {", + " let x;", + " var y;", + " }", + "}" + ].join("\n"), + parserOptions: { + ecmaVersion: 2022 + } + }, + { + code: [ + "class C {", + " static {", + " foo();", + " let x;", + " }", + "}" + ].join("\n"), + parserOptions: { + ecmaVersion: 2022 + } } ], @@ -427,6 +505,78 @@ ruleTester.run("vars-on-top", rule, { sourceType: "module" }, errors: [error] + }, + { + code: [ + "class C {", + " static {", + " foo();", + " var x;", + " }", + "}" + ].join("\n"), + parserOptions: { + ecmaVersion: 2022 + }, + errors: [error] + }, + { + code: [ + "class C {", + " static {", + " 'use strict';", // static blocks do not have directives + " var x;", + " }", + "}" + ].join("\n"), + parserOptions: { + ecmaVersion: 2022 + }, + errors: [error] + }, + { + code: [ + "class C {", + " static {", + " var x;", + " foo();", + " var y;", + " }", + "}" + ].join("\n"), + parserOptions: { + ecmaVersion: 2022 + }, + errors: [{ ...error, line: 5 }] + }, + { + code: [ + "class C {", + " static {", + " if (foo) {", + " var x;", + " }", + " }", + "}" + ].join("\n"), + parserOptions: { + ecmaVersion: 2022 + }, + errors: [error] + }, + { + code: [ + "class C {", + " static {", + " if (foo)", + " var x;", + " }", + "}" + ].join("\n"), + parserOptions: { + ecmaVersion: 2022 + }, + errors: [error] } ] }); diff --git a/eslint/tests/lib/rules/wrap-regex.js b/eslint/tests/lib/rules/wrap-regex.js index 0ac92f2..6aea211 100644 --- a/eslint/tests/lib/rules/wrap-regex.js +++ b/eslint/tests/lib/rules/wrap-regex.js @@ -16,7 +16,6 @@ const rule = require("../../../lib/rules/wrap-regex"), // Tests //------------------------------------------------------------------------------ - const ruleTester = new RuleTester(); ruleTester.run("wrap-regex", rule, { diff --git a/eslint/tests/lib/shared/config-validator.js b/eslint/tests/lib/shared/config-validator.js index 9ba99ae..61d616b 100644 --- a/eslint/tests/lib/shared/config-validator.js +++ b/eslint/tests/lib/shared/config-validator.js @@ -25,12 +25,13 @@ const assert = require("chai").assert, { Linter } = require("../../../lib/linter"), validator = require("../../../lib/shared/config-validator"), Rules = require("../../../lib/linter/rules"); -const linter = new Linter(); //------------------------------------------------------------------------------ -// Tests +// Helpers //------------------------------------------------------------------------------ +const linter = new Linter(); + /** * Fake a rule object * @param {Object} context context passed to the rules by eslint @@ -101,6 +102,10 @@ const mockRequiredOptionsRule = { } }; +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + describe("Validator", () => { /** diff --git a/eslint/tests/lib/shared/traverser.js b/eslint/tests/lib/shared/traverser.js index 5fbfb86..8e6ef28 100644 --- a/eslint/tests/lib/shared/traverser.js +++ b/eslint/tests/lib/shared/traverser.js @@ -1,8 +1,16 @@ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + const assert = require("chai").assert; const Traverser = require("../../../lib/shared/traverser"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + describe("Traverser", () => { it("traverses all keys except 'parent', 'leadingComments', and 'trailingComments'", () => { const traverser = new Traverser(); diff --git a/eslint/tests/lib/unsupported-api.js b/eslint/tests/lib/unsupported-api.js new file mode 100644 index 0000000..dd88be5 --- /dev/null +++ b/eslint/tests/lib/unsupported-api.js @@ -0,0 +1,30 @@ +/** + * @fileoverview Tests for unsupported-api. + * @author Nicholas C. Zakas + */ + +"use strict"; + +//----------------------------------------------------------------------------- +// Requirements +//----------------------------------------------------------------------------- + +const assert = require("chai").assert, + { LazyLoadingRuleMap } = require("../../lib/rules/utils/lazy-loading-rule-map"), + api = require("../../lib/unsupported-api"); + +//----------------------------------------------------------------------------- +// Tests +//----------------------------------------------------------------------------- + +describe("unsupported-api", () => { + + it("should have FileEnumerator exposed", () => { + assert.isFunction(api.FileEnumerator); + }); + + it("should have builtinRules exposed", () => { + assert.instanceOf(api.builtinRules, LazyLoadingRuleMap); + }); + +}); diff --git a/eslint/tests/tools/code-sample-minimizer.js b/eslint/tests/tools/code-sample-minimizer.js index 9a56c8c..84cbea7 100644 --- a/eslint/tests/tools/code-sample-minimizer.js +++ b/eslint/tests/tools/code-sample-minimizer.js @@ -1,8 +1,16 @@ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + const { assert } = require("chai"); const reduceBadExampleSize = require("../../tools/code-sample-minimizer"); +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + describe("reduceBadExampleSize()", () => { it("extracts relevant part of deeply nested code", () => { const initialCode = ` diff --git a/eslint/tests/tools/internal-rules/consistent-docs-url.js b/eslint/tests/tools/internal-rules/consistent-docs-url.js deleted file mode 100644 index 4b3d3a0..0000000 --- a/eslint/tests/tools/internal-rules/consistent-docs-url.js +++ /dev/null @@ -1,105 +0,0 @@ -/** - * @fileoverview Tests for internal-consistent-docs-url rule. - * @author Patrick McElhaney - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const rule = require("../../../tools/internal-rules/consistent-docs-url"), - { RuleTester } = require("../../../lib/rule-tester"); - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -const ruleTester = new RuleTester(); - -ruleTester.run("consistent-docs-url", rule, { - valid: [ - - // wrong exports format: "internal-no-invalid-meta" reports this already - [ - "module.exports = function(context) {", - " return {", - " Program: function(node) {}", - " };", - "};" - ].join("\n"), - [ - "module.exports = {", - " meta: {", - " docs: {", - " url: 'https://eslint.org/docs/rules/'", - " }", - " },", - " create: function(context) {", - " return {};", - " }", - "};" - ].join("\n") - ], - invalid: [ - { - code: [ - "module.exports = {", - " meta: {", - " },", - - " create: function(context) {", - " return {};", - " }", - "};" - ].join("\n"), - errors: [{ - messageId: "missingMetaDocs", - line: 2, - column: 5 - }] - }, - { - code: [ - "module.exports = {", - " meta: {", - " docs: {}", - " },", - - " create: function(context) {", - " return {};", - " }", - "};" - ].join("\n"), - errors: [{ - messageId: "missingMetaDocsUrl", - line: 3, - column: 9 - }] - }, - { - code: [ - "module.exports = {", - " meta: {", - " docs: {", - " url: 'http://example.com/wrong-url'", - " }", - " },", - " create: function(context) {", - " return {};", - " }", - "};" - ].join("\n"), - errors: [{ - messageId: "incorrectUrl", - data: { - expected: "https://eslint.org/docs/rules/", - url: "http://example.com/wrong-url" - }, - line: 4, - column: 18 - }] - } - ] -}); diff --git a/eslint/tests/tools/internal-rules/consistent-meta-messages.js b/eslint/tests/tools/internal-rules/consistent-meta-messages.js deleted file mode 100644 index 4df8be9..0000000 --- a/eslint/tests/tools/internal-rules/consistent-meta-messages.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @fileoverview Tests for consistent-meta-messages rule. - * @author 薛定谔的猫 - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const rule = require("../../../tools/internal-rules/consistent-meta-messages"); -const { RuleTester } = require("../../../lib/rule-tester"); - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -const ruleTester = new RuleTester(); - -ruleTester.run("consistent-meta-messages", rule, { - valid: [ - `module.exports = { - meta: { - messages: {unexpected: "an error occurs."} - } - };` - ], - invalid: [ - { - code: ` - module.exports = { - meta: {} - };`, - errors: [{ messageId: "expectedMessages" }] - } - ] -}); diff --git a/eslint/tests/tools/internal-rules/multiline-comment-style.js b/eslint/tests/tools/internal-rules/multiline-comment-style.js index d6dcff7..e4b104c 100644 --- a/eslint/tests/tools/internal-rules/multiline-comment-style.js +++ b/eslint/tests/tools/internal-rules/multiline-comment-style.js @@ -1,7 +1,16 @@ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + const rule = require("../../../tools/internal-rules/multiline-comment-style"); const { RuleTester } = require("../../../lib/rule-tester"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + const ruleTester = new RuleTester(); ruleTester.run("internal-rules/multiline-comment-style", rule, { diff --git a/eslint/tests/tools/internal-rules/no-invalid-meta.js b/eslint/tests/tools/internal-rules/no-invalid-meta.js index a5ff4d5..5013e4b 100644 --- a/eslint/tests/tools/internal-rules/no-invalid-meta.js +++ b/eslint/tests/tools/internal-rules/no-invalid-meta.js @@ -27,7 +27,6 @@ ruleTester.run("no-invalid-meta", rule, { " meta: {", " docs: {", " description: 'some rule',", - " category: 'Internal',", " recommended: false", " },", " schema: []", @@ -51,7 +50,6 @@ ruleTester.run("no-invalid-meta", rule, { " meta: {", " docs: {", " description: 'some rule',", - " category: 'Internal',", " recommended: false", " },", " schema: []", @@ -73,7 +71,6 @@ ruleTester.run("no-invalid-meta", rule, { " meta: {", " docs: {", " description: 'some rule',", - " category: 'Internal',", " recommended: false", " },", " schema: [],", @@ -96,20 +93,6 @@ ruleTester.run("no-invalid-meta", rule, { ].join("\n") ], invalid: [ - { - code: [ - "module.exports = function(context) {", - " return {", - " Program: function(node) {}", - " };", - "};" - ].join("\n"), - errors: [{ - messageId: "incorrectExport", - line: 1, - column: 18 - }] - }, { code: [ "module.exports = {", @@ -164,61 +147,12 @@ ruleTester.run("no-invalid-meta", rule, { column: 5 }] }, - { - code: [ - "module.exports = {", - " meta: {", - " docs: {", - " category: 'Internal',", - " recommended: false", - " },", - " schema: []", - " },", - - " create: function(context) {", - " return {", - " Program: function(node) {}", - " };", - " }", - "};" - ].join("\n"), - errors: [{ - messageId: "missingMetaDocsDescription", - line: 2, - column: 5 - }] - }, - { - code: [ - "module.exports = {", - " meta: {", - " docs: {", - " description: 'some rule',", - " recommended: false", - " },", - " schema: []", - " },", - - " create: function(context) {", - " return {", - " Program: function(node) {}", - " };", - " }", - "};" - ].join("\n"), - errors: [{ - messageId: "missingMetaDocsCategory", - line: 2, - column: 5 - }] - }, { code: [ "module.exports = {", " meta: {", " docs: {", " description: 'some rule',", - " category: 'Internal'", " },", " schema: []", " },", @@ -236,30 +170,6 @@ ruleTester.run("no-invalid-meta", rule, { column: 5 }] }, - { - code: [ - "module.exports = {", - " meta: {", - " docs: {", - " description: 'some rule',", - " category: 'Internal',", - " recommended: false", - " }", - " },", - - " create: function(context) {", - " return {", - " Program: function(node) {}", - " };", - " }", - "};" - ].join("\n"), - errors: [{ - messageId: "missingMetaSchema", - line: 2, - column: 5 - }] - }, { code: "", errors: [{ diff --git a/eslint/tools/internal-rules/consistent-docs-url.js b/eslint/tools/internal-rules/consistent-docs-url.js deleted file mode 100644 index 052fe55..0000000 --- a/eslint/tools/internal-rules/consistent-docs-url.js +++ /dev/null @@ -1,119 +0,0 @@ -/** - * @fileoverview Internal rule to enforce meta.docs.url conventions. - * @author Patrick McElhaney - */ - -"use strict"; - -const path = require("path"); - -//------------------------------------------------------------------------------ -// Helpers -//------------------------------------------------------------------------------ - -/** - * Gets the property of the Object node passed in that has the name specified. - * @param {string} property Name of the property to return. - * @param {ASTNode} node The ObjectExpression node. - * @returns {ASTNode} The Property node or null if not found. - */ -function getPropertyFromObject(property, node) { - const properties = node.properties; - - if (!Array.isArray(properties)) { - - // if properties is not an array, "internal-no-invalid-meta" will already report this. - return null; - } - - for (let i = 0; i < properties.length; i++) { - if (properties[i].key.name === property) { - return properties[i]; - } - } - - return null; -} - -/** - * Verifies that the meta.docs.url property is present and has the correct value. - * @param {RuleContext} context The ESLint rule context. - * @param {ASTNode} exportsNode ObjectExpression node that the rule exports. - * @returns {void} - */ -function checkMetaDocsUrl(context, exportsNode) { - if (exportsNode.type !== "ObjectExpression") { - - // if the exported node is not the correct format, "internal-no-invalid-meta" will already report this. - return; - } - - const metaProperty = getPropertyFromObject("meta", exportsNode); - const metaDocs = metaProperty && getPropertyFromObject("docs", metaProperty.value); - const metaDocsUrl = metaDocs && getPropertyFromObject("url", metaDocs.value); - - if (!metaDocs) { - context.report({ - node: metaProperty, - messageId: "missingMetaDocs" - }); - return; - } - - if (!metaDocsUrl) { - context.report({ - node: metaDocs, - messageId: "missingMetaDocsUrl" - }); - return; - } - - const ruleId = path.basename(context.getFilename().replace(/.js$/u, "")); - const expected = `https://eslint.org/docs/rules/${ruleId}`; - const url = metaDocsUrl.value.value; - - if (url !== expected) { - context.report({ - node: metaDocsUrl.value, - messageId: "incorrectUrl", - data: { expected, url } - }); - } - -} - -//------------------------------------------------------------------------------ -// Rule Definition -//------------------------------------------------------------------------------ - -module.exports = { - meta: { - docs: { - description: "enforce correct conventions of `meta.docs.url` property in core rules", - category: "Internal", - recommended: false - }, - type: "suggestion", - schema: [], - messages: { - missingMetaDocs: "Rule is missing a meta.docs property.", - missingMetaDocsUrl: "Rule is missing a meta.docs.url property.", - incorrectUrl: 'Incorrect url. Expected "{{ expected }}" but got "{{ url }}".' - } - }, - - create(context) { - return { - AssignmentExpression(node) { - if (node.left && - node.right && - node.left.type === "MemberExpression" && - node.left.object.name === "module" && - node.left.property.name === "exports") { - - checkMetaDocsUrl(context, node.right); - } - } - }; - } -}; diff --git a/eslint/tools/internal-rules/consistent-meta-messages.js b/eslint/tools/internal-rules/consistent-meta-messages.js deleted file mode 100644 index b094c86..0000000 --- a/eslint/tools/internal-rules/consistent-meta-messages.js +++ /dev/null @@ -1,80 +0,0 @@ -/** - * @fileoverview A rule to enforce using `meta.messages` property in core rules - * @author 薛定谔的猫 - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Helpers -//------------------------------------------------------------------------------ - -/** - * Gets the property of the Object node passed in that has the name specified. - * @param {string} property Name of the property to return. - * @param {ASTNode} node The ObjectExpression node. - * @returns {ASTNode} The Property node or null if not found. - */ -function getPropertyFromObject(property, node) { - const properties = node.properties; - - for (let i = 0; i < properties.length; i++) { - if (properties[i].key.name === property) { - return properties[i]; - } - } - - return null; -} - -/** - * Verifies that the meta.messages property is present. - * TODO: check it has the correct value - * @param {RuleContext} context The ESLint rule context. - * @param {ASTNode} exportsNode ObjectExpression node that the rule exports. - * @returns {void} - */ -function checkMetaMessages(context, exportsNode) { - if (exportsNode.type !== "ObjectExpression") { - - // if the exported node is not the correct format, "internal-no-invalid-meta" will already report this. - return; - } - - const metaProperty = getPropertyFromObject("meta", exportsNode); - const messages = metaProperty && getPropertyFromObject("messages", metaProperty.value); - - if (!messages) { - context.report({ - node: metaProperty, - messageId: "expectedMessages" - }); - } -} - -//------------------------------------------------------------------------------ -// Rule Definition -//------------------------------------------------------------------------------ - -module.exports = { - meta: { - docs: { - description: "enforce using `meta.messages` property in core rules", - category: "Internal", - recommended: false - }, - schema: [], - type: "suggestion", - messages: { - expectedMessages: "Expected `meta.messages` property." - } - }, - - create(context) { - return { - "AssignmentExpression[left.object.name='module'][left.property.name='exports']"(node) { - checkMetaMessages(context, node.right); - } - }; - } -}; diff --git a/eslint/tools/internal-rules/multiline-comment-style.js b/eslint/tools/internal-rules/multiline-comment-style.js index b0a0c9d..4492b98 100644 --- a/eslint/tools/internal-rules/multiline-comment-style.js +++ b/eslint/tools/internal-rules/multiline-comment-style.js @@ -13,7 +13,7 @@ const multilineCommentStyle = require("../../lib/rules/multiline-comment-style") //------------------------------------------------------------------------------ // The `no-invalid-meta` internal rule has a false positive here. -// eslint-disable-next-line internal-rules/no-invalid-meta +// eslint-disable-next-line internal-rules/no-invalid-meta -- Using rule composer module.exports = ruleComposer.filterReports( multilineCommentStyle, (problem, metadata) => { diff --git a/eslint/tools/internal-rules/no-invalid-meta.js b/eslint/tools/internal-rules/no-invalid-meta.js index e5a74b3..2bfa2e2 100644 --- a/eslint/tools/internal-rules/no-invalid-meta.js +++ b/eslint/tools/internal-rules/no-invalid-meta.js @@ -50,28 +50,6 @@ function hasMetaDocs(metaPropertyNode) { return Boolean(getPropertyFromObject("docs", metaPropertyNode.value)); } -/** - * Whether this `meta` ObjectExpression has a `docs.description` property defined or not. - * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule. - * @returns {boolean} `true` if a `docs.description` property exists. - */ -function hasMetaDocsDescription(metaPropertyNode) { - const metaDocs = getPropertyFromObject("docs", metaPropertyNode.value); - - return metaDocs && getPropertyFromObject("description", metaDocs.value); -} - -/** - * Whether this `meta` ObjectExpression has a `docs.category` property defined or not. - * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule. - * @returns {boolean} `true` if a `docs.category` property exists. - */ -function hasMetaDocsCategory(metaPropertyNode) { - const metaDocs = getPropertyFromObject("docs", metaPropertyNode.value); - - return metaDocs && getPropertyFromObject("category", metaDocs.value); -} - /** * Whether this `meta` ObjectExpression has a `docs.recommended` property defined or not. * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule. @@ -83,15 +61,6 @@ function hasMetaDocsRecommended(metaPropertyNode) { return metaDocs && getPropertyFromObject("recommended", metaDocs.value); } -/** - * Whether this `meta` ObjectExpression has a `schema` property defined or not. - * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule. - * @returns {boolean} `true` if a `schema` property exists. - */ -function hasMetaSchema(metaPropertyNode) { - return getPropertyFromObject("schema", metaPropertyNode.value); -} - /** * Checks the validity of the meta definition of this rule and reports any errors found. * @param {RuleContext} context The ESLint rule context. @@ -111,33 +80,9 @@ function checkMetaValidity(context, exportsNode) { return; } - if (!hasMetaDocsDescription(metaProperty)) { - context.report({ node: metaProperty, messageId: "missingMetaDocsDescription" }); - return; - } - - if (!hasMetaDocsCategory(metaProperty)) { - context.report({ node: metaProperty, messageId: "missingMetaDocsCategory" }); - return; - } - if (!hasMetaDocsRecommended(metaProperty)) { context.report({ node: metaProperty, messageId: "missingMetaDocsRecommended" }); - return; } - - if (!hasMetaSchema(metaProperty)) { - context.report({ node: metaProperty, messageId: "missingMetaSchema" }); - } -} - -/** - * Whether this node is the correct format for a rule definition or not. - * @param {ASTNode} node node that the rule exports. - * @returns {boolean} `true` if the exported node is the correct format for a rule definition - */ -function isCorrectExportsFormat(node) { - return node.type === "ObjectExpression"; } //------------------------------------------------------------------------------ @@ -148,7 +93,6 @@ module.exports = { meta: { docs: { description: "enforce correct use of `meta` property in core rules", - category: "Internal", recommended: false }, type: "problem", @@ -156,12 +100,8 @@ module.exports = { messages: { missingMeta: "Rule is missing a meta property.", missingMetaDocs: "Rule is missing a meta.docs property.", - missingMetaDocsDescription: "Rule is missing a meta.docs.description property.", - missingMetaDocsCategory: "Rule is missing a meta.docs.category property.", missingMetaDocsRecommended: "Rule is missing a meta.docs.recommended property.", - missingMetaSchema: "Rule is missing a meta.schema property.", - noExport: "Rule does not export anything. Make sure rule exports an object according to new rule format.", - incorrectExport: "Rule does not export an Object. Make sure the rule follows the new rule format." + noExport: "Rule does not export anything. Make sure rule exports an object according to new rule format." } }, @@ -186,11 +126,6 @@ module.exports = { node, messageId: "noExport" }); - } else if (!isCorrectExportsFormat(exportsNode)) { - context.report({ - node: exportsNode, - messageId: "incorrectExport" - }); } else { checkMetaValidity(context, exportsNode); } diff --git a/eslint/tools/internal-testers/event-generator-tester.js b/eslint/tools/internal-testers/event-generator-tester.js index a5ec8e1..ce4449a 100644 --- a/eslint/tools/internal-testers/event-generator-tester.js +++ b/eslint/tools/internal-testers/event-generator-tester.js @@ -4,7 +4,7 @@ */ "use strict"; -/* global describe, it */ +/* eslint-env mocha -- Mocha */ //------------------------------------------------------------------------------ // Requirements diff --git a/eslint/tools/rule-types.json b/eslint/tools/rule-types.json index d35446d..4a71a8b 100644 --- a/eslint/tools/rule-types.json +++ b/eslint/tools/rule-types.json @@ -208,6 +208,7 @@ "no-unsafe-optional-chaining": "problem", "no-unused-expressions": "suggestion", "no-unused-labels": "suggestion", + "no-unused-private-class-members": "problem", "no-unused-vars": "problem", "no-use-before-define": "problem", "no-useless-backreference": "problem", diff --git a/eslint/tools/update-readme.js b/eslint/tools/update-readme.js index 24fe3c0..55efb6f 100644 --- a/eslint/tools/update-readme.js +++ b/eslint/tools/update-readme.js @@ -48,7 +48,7 @@ delete allSponsors.backers; * @returns {string} The HTML for the members list. */ function formatTeamMembers(members) { - /* eslint-disable indent*/ + /* eslint-disable indent -- Allow deeper template substitution indent */ return stripIndents` ${ members.map((member, index) => `${(index + 1) % 9 === 0 ? "" : ""}`).join("") }
@@ -58,7 +58,7 @@ function formatTeamMembers(members) {
`; - /* eslint-enable indent*/ + /* eslint-enable indent -- Allow deeper template substitution indent */ } /** @@ -69,7 +69,7 @@ function formatTeamMembers(members) { function formatSponsors(sponsors) { const nonEmptySponsors = Object.keys(sponsors).filter(tier => sponsors[tier].length > 0); - /* eslint-disable indent*/ + /* eslint-disable indent -- Allow deeper template substitution indent */ return stripIndents` ${ nonEmptySponsors.map(tier => `

${tier[0].toUpperCase()}${tier.slice(1)} Sponsors

@@ -78,7 +78,7 @@ function formatSponsors(sponsors) { }

`).join("") } `; - /* eslint-enable indent*/ + /* eslint-enable indent -- Allow deeper template substitution indent */ } //----------------------------------------------------------------------------- diff --git a/eslint/tools/update-rule-types.js b/eslint/tools/update-rule-types.js index 0cb40a7..6a68586 100644 --- a/eslint/tools/update-rule-types.js +++ b/eslint/tools/update-rule-types.js @@ -30,29 +30,11 @@ module.exports = (fileInfo, api) => { } const typeNode = metaNode.value.properties.find(node => node.key.name === "type"); - const docsNode = metaNode.value.properties.find(node => node.key.name === "docs"); - const categoryNode = docsNode.value.properties.find(node => node.key.name === "category").value; let ruleType; - // the rule-types.json file takes highest priority if (ruleName in ruleTypes) { ruleType = ruleTypes[ruleName]; - } else { - - // otherwise fallback to category - switch (categoryNode.value) { - case "Stylistic Issues": - ruleType = "style"; - break; - - case "Possible Errors": - ruleType = "problem"; - break; - - default: - ruleType = "suggestion"; - } } if (typeNode) { -- 2.39.2